mirror of
https://github.com/kremalicious/umami.git
synced 2024-06-30 05:31:50 +02:00
commit
2b4ddb5388
|
@ -16,7 +16,8 @@
|
|||
"rules": {
|
||||
"react/display-name": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off"
|
||||
"react/prop-types": "off",
|
||||
"import/no-anonymous-default-export": "off"
|
||||
},
|
||||
"globals": {
|
||||
"React": "writable"
|
||||
|
|
|
@ -11,6 +11,7 @@ function MenuButton({
|
|||
value,
|
||||
options,
|
||||
buttonClassName,
|
||||
buttonVariant,
|
||||
menuClassName,
|
||||
menuPosition = 'bottom',
|
||||
menuAlign = 'right',
|
||||
|
@ -43,7 +44,7 @@ function MenuButton({
|
|||
icon={icon}
|
||||
className={classNames(styles.button, buttonClassName, { [styles.open]: showMenu })}
|
||||
onClick={toggleMenu}
|
||||
variant="light"
|
||||
variant={buttonVariant}
|
||||
>
|
||||
{!hideLabel && (
|
||||
<div className={styles.text}>{renderValue ? renderValue(selectedOption) : value}</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import classNames from 'classnames';
|
||||
import Link from './Link';
|
||||
import Button from './Button';
|
||||
import XMark from 'assets/xmark.svg';
|
||||
|
@ -5,9 +6,9 @@ import styles from './MobileMenu.module.css';
|
|||
|
||||
export default function MobileMenu({ items = [], onClose }) {
|
||||
return (
|
||||
<div className={styles.menu}>
|
||||
<div className={classNames(styles.menu, 'container')}>
|
||||
<div className={styles.header}>
|
||||
<Button className={styles.button} icon={<XMark />} onClick={onClose} />
|
||||
<Button icon={<XMark />} onClick={onClose} />
|
||||
</div>
|
||||
<div className={styles.items}>
|
||||
{items.map(({ label, value }) => (
|
||||
|
|
|
@ -32,13 +32,10 @@
|
|||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export default function UpdateNotice() {
|
|||
values={{ version: `v${latest}` }}
|
||||
/>
|
||||
</div>
|
||||
<ButtonLayout>
|
||||
<ButtonLayout className={styles.buttons}>
|
||||
<Button size="xsmall" variant="action" onClick={handleViewClick}>
|
||||
<FormattedMessage id="label.view-details" defaultMessage="View details" />
|
||||
</Button>
|
||||
|
|
|
@ -2,12 +2,17 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 600;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 0;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useRouter } from 'next/router';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
|
@ -14,6 +15,7 @@ import { HOMEPAGE_URL } from 'lib/constants';
|
|||
|
||||
export default function Header() {
|
||||
const { user } = useUser();
|
||||
const { pathname } = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -21,7 +23,7 @@ export default function Header() {
|
|||
<header className={classNames(styles.header, 'row')}>
|
||||
<div className={styles.title}>
|
||||
<Icon icon={<Logo />} size="large" className={styles.logo} />
|
||||
<Link href={user ? '/' : HOMEPAGE_URL}>umami</Link>
|
||||
<Link href={pathname.includes('/share') ? HOMEPAGE_URL : '/'}>umami</Link>
|
||||
</div>
|
||||
<HamburgerButton />
|
||||
{user && (
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-large);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -17,7 +18,7 @@
|
|||
}
|
||||
|
||||
.links {
|
||||
flex: 1;
|
||||
flex: 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -30,6 +31,7 @@
|
|||
}
|
||||
|
||||
.buttons {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
@ -39,6 +41,7 @@
|
|||
.header .buttons {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.links {
|
||||
order: 2;
|
||||
margin: 20px 0;
|
||||
|
@ -48,7 +51,7 @@
|
|||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.header {
|
||||
padding: 0 15px;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.buttons,
|
||||
|
|
|
@ -14,9 +14,7 @@ export default function Layout({ title, children, header = true, footer = true }
|
|||
</Head>
|
||||
|
||||
{header && <Header />}
|
||||
<main className="container" dir={dir}>
|
||||
{children}
|
||||
</main>
|
||||
<main>{children}</main>
|
||||
{footer && <Footer />}
|
||||
<div id="__modals" dir={dir} />
|
||||
</>
|
||||
|
|
|
@ -9,7 +9,8 @@ import styles from './ActiveUsers.module.css';
|
|||
|
||||
export default function ActiveUsers({ websiteId, className, value, interval = 60000 }) {
|
||||
const shareToken = useShareToken();
|
||||
const { data } = useFetch(!value && `/website/${websiteId}/active`, {
|
||||
const url = value !== undefined && websiteId ? `/website/${websiteId}/active` : null;
|
||||
const { data } = useFetch(url, {
|
||||
interval,
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
});
|
||||
|
|
|
@ -10,10 +10,11 @@ import useShareToken from 'hooks/useShareToken';
|
|||
import { EVENT_COLORS, TOKEN_HEADER } from 'lib/constants';
|
||||
|
||||
export default function EventsChart({ websiteId, className, token }) {
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit, modified } = dateRange;
|
||||
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
|
||||
const [timezone] = useTimezone();
|
||||
const { query } = usePageQuery();
|
||||
const {
|
||||
query: { url, eventType },
|
||||
} = usePageQuery();
|
||||
const shareToken = useShareToken();
|
||||
|
||||
const { data, loading } = useFetch(
|
||||
|
@ -24,12 +25,13 @@ export default function EventsChart({ websiteId, className, token }) {
|
|||
end_at: +endDate,
|
||||
unit,
|
||||
tz: timezone,
|
||||
url: query.url,
|
||||
url,
|
||||
event_type: eventType,
|
||||
token,
|
||||
},
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
},
|
||||
[modified],
|
||||
[modified, eventType],
|
||||
);
|
||||
|
||||
const datasets = useMemo(() => {
|
||||
|
|
|
@ -4,16 +4,18 @@ import MetricsTable from './MetricsTable';
|
|||
import Tag from 'components/common/Tag';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import { eventTypeFilter } from 'lib/filters';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import styles from './EventsTable.module.css';
|
||||
|
||||
const EVENT_FILTER_DEFAULT = {
|
||||
value: 'EVENT_FILTER_DEFAULT',
|
||||
value: 'all',
|
||||
label: <FormattedMessage id="label.all-events" defaultMessage="All events" />,
|
||||
};
|
||||
|
||||
export default function EventsTable({ websiteId, ...props }) {
|
||||
const [eventType, setEventType] = useState(EVENT_FILTER_DEFAULT.value);
|
||||
const [eventTypes, setEventTypes] = useState([]);
|
||||
const { resolve, router } = usePageQuery();
|
||||
|
||||
const dropDownOptions = [EVENT_FILTER_DEFAULT, ...eventTypes.map(t => ({ value: t, label: t }))];
|
||||
|
||||
|
@ -22,11 +24,16 @@ export default function EventsTable({ websiteId, ...props }) {
|
|||
props.onDataLoad?.(data);
|
||||
}
|
||||
|
||||
function handleChange(value) {
|
||||
router.replace(resolve({ eventType: value === 'all' ? undefined : value }));
|
||||
setEventType(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{eventTypes?.length > 1 && (
|
||||
<div className={styles.filter}>
|
||||
<DropDown value={eventType} options={dropDownOptions} onChange={setEventType} />
|
||||
<DropDown value={eventType} options={dropDownOptions} onChange={handleChange} />
|
||||
</div>
|
||||
)}
|
||||
<MetricsTable
|
||||
|
|
|
@ -32,11 +32,11 @@ export default function WebsiteHeader({ websiteId, title, domain, showLink = fal
|
|||
|
||||
return (
|
||||
<PageHeader className="row">
|
||||
<div className={classNames(styles.title, 'col-12 col-lg-4')}>{header}</div>
|
||||
<div className={classNames(styles.active, 'col-6 col-lg-4')}>
|
||||
<div className={classNames(styles.title, 'col-10 col-lg-4 order-1 order-lg-1')}>{header}</div>
|
||||
<div className={classNames(styles.active, 'col-12 col-lg-4 order-3 order-lg-2')}>
|
||||
<ActiveUsers websiteId={websiteId} />
|
||||
</div>
|
||||
<div className="col-6 col-lg-4">
|
||||
<div className="col-2 col-lg-4 order-2 order-lg-3">
|
||||
<ButtonLayout align="right">
|
||||
<RefreshButton websiteId={websiteId} />
|
||||
{showLink && (
|
||||
|
|
|
@ -25,4 +25,8 @@
|
|||
.active {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
a.link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
46
components/pages/Dashboard.js
Normal file
46
components/pages/Dashboard.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import WebsiteList from 'components/pages/WebsiteList';
|
||||
import Button from 'components/common/Button';
|
||||
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useStore from 'store/app';
|
||||
import styles from './WebsiteList.module.css';
|
||||
|
||||
const selector = state => state.dashboard;
|
||||
|
||||
export default function Dashboard() {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
const userId = id?.[0];
|
||||
const store = useStore(selector);
|
||||
const { showCharts, limit } = store;
|
||||
const [max, setMax] = useState(limit);
|
||||
const { data } = useFetch('/websites', { params: { user_id: userId } });
|
||||
|
||||
function handleMore() {
|
||||
setMax(max + limit);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader>
|
||||
<div>Dashboard</div>
|
||||
<DashboardSettingsButton />
|
||||
</PageHeader>
|
||||
<WebsiteList websites={data} showCharts={showCharts} limit={max} />
|
||||
{max < data.length && (
|
||||
<Button className={styles.button} onClick={handleMore}>
|
||||
<FormattedMessage id="label.more" defaultMessage="More" />
|
||||
</Button>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
|
@ -44,7 +44,13 @@ export default function TestConsole() {
|
|||
<Page>
|
||||
<Head>
|
||||
{typeof window !== 'undefined' && website && (
|
||||
<script async defer data-website-id={website.website_uuid} src={`${basePath}/umami.js`} />
|
||||
<script
|
||||
async
|
||||
defer
|
||||
data-website-id={website.website_uuid}
|
||||
src={`${basePath}/umami.js`}
|
||||
data-cache="true"
|
||||
/>
|
||||
)}
|
||||
</Head>
|
||||
<PageHeader>
|
||||
|
@ -68,7 +74,7 @@ export default function TestConsole() {
|
|||
{show && (
|
||||
<div className={classNames(styles.test, 'row')}>
|
||||
<div className="col-4">
|
||||
<PageHeader>Page links</PageHeader>Nmo
|
||||
<PageHeader>Page links</PageHeader>
|
||||
<div>
|
||||
<Link href={`?page=1`}>
|
||||
<a>page one</a>
|
||||
|
|
|
@ -1,32 +1,13 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Link from 'components/common/Link';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
import Page from 'components/layout/Page';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
|
||||
import Button from 'components/common/Button';
|
||||
import useStore from 'store/app';
|
||||
import Arrow from 'assets/arrow-right.svg';
|
||||
import styles from './WebsiteList.module.css';
|
||||
|
||||
const selector = state => state.dashboard;
|
||||
|
||||
export default function WebsiteList({ userId }) {
|
||||
const { data } = useFetch('/websites', { params: { user_id: userId } });
|
||||
const { showCharts, limit } = useStore(selector);
|
||||
const [max, setMax] = useState(limit);
|
||||
|
||||
function handleMore() {
|
||||
setMax(max + limit);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
export default function WebsiteList({ websites, showCharts, limit }) {
|
||||
if (websites.length === 0) {
|
||||
return (
|
||||
<Page>
|
||||
<EmptyPlaceholder
|
||||
|
@ -46,12 +27,9 @@ export default function WebsiteList({ userId }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className={styles.menubar}>
|
||||
<DashboardSettingsButton />
|
||||
</div>
|
||||
{data.map(({ website_id, name, domain }, index) =>
|
||||
index < max ? (
|
||||
<div>
|
||||
{websites.map(({ website_id, name, domain }, index) =>
|
||||
index < limit ? (
|
||||
<div key={website_id} className={styles.website}>
|
||||
<WebsiteChart
|
||||
websiteId={website_id}
|
||||
|
@ -63,11 +41,6 @@ export default function WebsiteList({ userId }) {
|
|||
</div>
|
||||
) : null,
|
||||
)}
|
||||
{max < data.length && (
|
||||
<Button className={styles.button} onClick={handleMore}>
|
||||
<FormattedMessage id="label.more" defaultMessage="More" />
|
||||
</Button>
|
||||
)}
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,17 +7,5 @@
|
|||
|
||||
.website:last-child {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.menubar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
align-self: center;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
import Gear from 'assets/gear.svg';
|
||||
import useStore, { setDashboard } from 'store/app';
|
||||
import useStore, { setDashboard, defaultDashboardConfig } from 'store/app';
|
||||
|
||||
const selector = state => state.dashboard;
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default function DashboardSettingsButton() {
|
|||
|
||||
function handleSelect(value) {
|
||||
if (value === 'charts') {
|
||||
setDashboard({ showCharts: !settings.showCharts });
|
||||
setDashboard({ ...defaultDashboardConfig, showCharts: !settings.showCharts });
|
||||
}
|
||||
//setDashboard(value);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export default function LanguageButton() {
|
|||
options={menuOptions}
|
||||
value={locale}
|
||||
menuClassName={styles.menu}
|
||||
buttonVariant="light"
|
||||
onSelect={handleSelect}
|
||||
hideLabel
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useRouter } from 'next/router';
|
|||
import MenuButton from 'components/common/MenuButton';
|
||||
import Icon from 'components/common/Icon';
|
||||
import User from 'assets/user.svg';
|
||||
import Chevron from 'assets/chevron-down.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
import { removeItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
|
@ -42,9 +41,10 @@ export default function UserButton() {
|
|||
return (
|
||||
<MenuButton
|
||||
icon={<Icon icon={<User />} size="large" />}
|
||||
value={<Icon icon={<Chevron />} size="small" />}
|
||||
buttonVariant="light"
|
||||
options={menuOptions}
|
||||
onSelect={handleSelect}
|
||||
hideLabel
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"metrics.device.tablet",
|
||||
"metrics.referrers"
|
||||
],
|
||||
"en-GB": "*",
|
||||
"fr-FR": ["metrics.actions", "metrics.pages"],
|
||||
"lt-LT": [
|
||||
"metrics.device.desktop",
|
||||
|
@ -31,6 +32,6 @@
|
|||
"message.powered-by",
|
||||
"metrics.device.desktop",
|
||||
"metrics.device.tablet",
|
||||
"metrics.filter.raw",
|
||||
],
|
||||
"metrics.filter.raw"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "لتتبع الاحصاىيات لـ {target}, ضع الكود التالي في منطقة {head} في موقعك.",
|
||||
"message.type-delete": "اكتب {delete} في الحقل التالي لتأكيد الحذف.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "اكتب {reset} في الحقل التالي لتأكيد الحذف.",
|
||||
"metrics.actions": "اجراءات",
|
||||
"metrics.average-visit-time": "متوسط وقت الزيارة",
|
||||
"metrics.bounce-rate": "معدل الارتداد",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Pro sledování návštěv na {target}, přidejte následující kód do {head} části vašeho webu.",
|
||||
"message.type-delete": "Napište {delete} pro potvrzení.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Napište {reset} pro potvrzení.",
|
||||
"metrics.actions": "Akce",
|
||||
"metrics.average-visit-time": "Průměrný čas návštěvy",
|
||||
"metrics.bounce-rate": "Okamžité opuštění",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "For at spore statistik for {target} skal du placere følgende kode i {head} sektionen på dit websted.",
|
||||
"message.type-delete": "Skriv {delete} i boksen nedenfor, for at bekræfte.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Skriv {reset} i boksen nedenfor, for at bekræfte.",
|
||||
"metrics.actions": "Handlinger",
|
||||
"metrics.average-visit-time": "Gennemsnitlig besøgstid",
|
||||
"metrics.bounce-rate": "Afvisningsprocent",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Για να παρακολουθείτε στατιστικά στοιχεία για {target}, τοποθετήστε τον ακόλουθο κώδικα στην ενότητα {head} του ιστότοπού σας.",
|
||||
"message.type-delete": "Πληκτρολογήστε {delete} στο παρακάτω πλαίσιο για επιβεβαίωση.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Πληκτρολογήστε {reset} στο παρακάτω πλαίσιο για επιβεβαίωση.",
|
||||
"metrics.actions": "Ενέργειες",
|
||||
"metrics.average-visit-time": "Μέσος χρόνος επίσκεψης",
|
||||
"metrics.bounce-rate": "Ποσοστό αναπήδησης",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "برای ردیابی آمار {target}, کد روبرو را در قسمت {head} وبسایت قرار دهید.",
|
||||
"message.type-delete": "جهت اطمینان '{delete}' را در کادر زیر بنویسید.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "جهت اطمینان '{reset}' را در کادر زیر بنویسید.",
|
||||
"metrics.actions": "اقدامات",
|
||||
"metrics.average-visit-time": "میانگین زمان بازدید",
|
||||
"metrics.bounce-rate": "نرخ Bounce",
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"label.add-website": "Lisää verkkosivu",
|
||||
"label.administrator": "Järjestelmänvalvoja",
|
||||
"label.all": "Kaikki",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-events": "Kaikki tapahtumat",
|
||||
"label.all-time": "Alusta lähtien",
|
||||
"label.all-websites": "Kaikki verkkosivut",
|
||||
"label.back": "Takaisin",
|
||||
"label.cancel": "Peruuta",
|
||||
|
@ -13,7 +13,7 @@
|
|||
"label.confirm-password": "Vahvista salasana",
|
||||
"label.copy-to-clipboard": "Kopioi leikepöydälle",
|
||||
"label.current-password": "Nykyinen salasana",
|
||||
"label.custom-range": "Mukautettu jakso",
|
||||
"label.custom-range": "Mukautettu ajanjakso",
|
||||
"label.dashboard": "Ohjauspaneeli",
|
||||
"label.date-range": "Ajanjakso",
|
||||
"label.default-date-range": "Oletusajanjakso",
|
||||
|
@ -28,30 +28,30 @@
|
|||
"label.enable-share-url": "Ota jakamisen URL-osoite käyttöön",
|
||||
"label.invalid": "Virheellinen",
|
||||
"label.invalid-domain": "Virheellinen verkkotunnus",
|
||||
"label.language": "Language",
|
||||
"label.last-days": "Viimeisimmät {x} päivät",
|
||||
"label.last-hours": "Viimeisimmät {x} tunnit",
|
||||
"label.language": "Kieli",
|
||||
"label.last-days": "Viimeisimmät {x} päivää",
|
||||
"label.last-hours": "Viimeisimmät {x} tuntia",
|
||||
"label.logged-in-as": "Kirjautuneena sisään nimellä {username}",
|
||||
"label.login": "Kirjaudu sisään",
|
||||
"label.logout": "Kirjaudu ulos",
|
||||
"label.more": "Lisää",
|
||||
"label.name": "Nimi",
|
||||
"label.new-password": "Uusi salasana",
|
||||
"label.owner": "Owner",
|
||||
"label.owner": "Omistaja",
|
||||
"label.password": "Salasana",
|
||||
"label.passwords-dont-match": "Salasanat eivät täsmää",
|
||||
"label.profile": "Profiili",
|
||||
"label.realtime": "Reaaliaikainen",
|
||||
"label.realtime": "Juuri nyt",
|
||||
"label.realtime-logs": "Reaaliaikaiset lokit",
|
||||
"label.refresh": "Päivitä",
|
||||
"label.required": "Vaaditaan",
|
||||
"label.reset": "Nollaa",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Nollaa tilastot",
|
||||
"label.save": "Tallenna",
|
||||
"label.settings": "Asetukset",
|
||||
"label.share-url": "Jaa URL",
|
||||
"label.single-day": "Yksi päivä",
|
||||
"label.theme": "Theme",
|
||||
"label.theme": "Teema",
|
||||
"label.this-month": "Tämä kuukausi",
|
||||
"label.this-week": "Tämä viikko",
|
||||
"label.this-year": "Tämä vuosi",
|
||||
|
@ -62,29 +62,29 @@
|
|||
"label.username": "Käyttäjänimi",
|
||||
"label.view-details": "Katso tiedot",
|
||||
"label.websites": "Verkkosivut",
|
||||
"message.active-users": "{x} nykyinen {x, plural, one {yksi} other {muut}}",
|
||||
"message.confirm-delete": "Haluatko varmasti poistaa {target}?",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.active-users": "{x} {x, plural, one {vierailija} other {vierailijaa}}",
|
||||
"message.confirm-delete": "Haluatko varmasti poistaa sivuston {target}?",
|
||||
"message.confirm-reset": "Haluatko varmasti poistaa sivuston {target} tilastot?",
|
||||
"message.copied": "Kopioitu!",
|
||||
"message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.",
|
||||
"message.failure": "Jotain meni väärin.",
|
||||
"message.failure": "Jotain meni pieleen.",
|
||||
"message.get-share-url": "Hanki jakamisen URL-osoite",
|
||||
"message.get-tracking-code": "Hanki seurantakoodi",
|
||||
"message.go-to-settings": "Mene asetuksiin",
|
||||
"message.incorrect-username-password": "Väärä käyttäjänimi/salasana.",
|
||||
"message.log.visitor": "Vierailija maasta {country} käyttäen selainta {browser} {os}-laitteella: {device}",
|
||||
"message.log.visitor": "Vierailija maasta {country} selaimella {browser} laitteella {os} {device}",
|
||||
"message.new-version-available": "Uusi versio umamista {version} on käytettävissä!",
|
||||
"message.no-data-available": "Tietoja ei ole käytettävissä.",
|
||||
"message.no-websites-configured": "Sinulla ei ole määritettyjä verkkosivustoja.",
|
||||
"message.page-not-found": "Sivua ei löydetty.",
|
||||
"message.powered-by": "Voimanlähteenä {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "Kaikki sivuston tilastot poistetaan, mutta seurantakoodi pysyy muuttumattomana.",
|
||||
"message.save-success": "Tallennettu onnistuneesti.",
|
||||
"message.share-url": "Tämä on julkisesti jaettu URL-osoitteelle {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Jos haluat seurata kohteen {target} tilastoja, aseta seuraava koodi verkkosivustosi {head} osioon.",
|
||||
"message.type-delete": "Kirjoita {delete} alla olevaan ruutuun vahvistaaksesi.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.share-url": "Tämä on julkisesti jaettu URL sivustolle {target}.",
|
||||
"message.toggle-charts": "Kytke kaaviot päälle/pois",
|
||||
"message.track-stats": "Jos haluat seurata sivuston {target} tilastoja, aseta seuraava koodi verkkosivustosi {head}-osioon.",
|
||||
"message.type-delete": "Kirjoita {delete} alla olevaan kenttään vahvistaaksesi.",
|
||||
"message.type-reset": "Kirjoita {reset} alla olevaan kenttään vahvistaaksesi.",
|
||||
"metrics.actions": "Toiminnat",
|
||||
"metrics.average-visit-time": "Keskimääräinen vierailuaika",
|
||||
"metrics.bounce-rate": "Välitön poistuminen",
|
||||
|
@ -92,19 +92,19 @@
|
|||
"metrics.countries": "Maat",
|
||||
"metrics.device.desktop": "Pöytäkone",
|
||||
"metrics.device.laptop": "Kannettava tietokone",
|
||||
"metrics.device.mobile": "Mobiili",
|
||||
"metrics.device.mobile": "Puhelin",
|
||||
"metrics.device.tablet": "Tabletti",
|
||||
"metrics.devices": "Laitteet",
|
||||
"metrics.events": "Tapahtumat",
|
||||
"metrics.filter.combined": "Yhdistetty",
|
||||
"metrics.filter.domain-only": "Vain verkkotunnus",
|
||||
"metrics.filter.raw": "Käsittelemätön",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.languages": "Kielet",
|
||||
"metrics.operating-systems": "Käyttöjärjestelmät",
|
||||
"metrics.page-views": "Sivun näyttökertoja",
|
||||
"metrics.page-views": "Sivun näyttökerrat",
|
||||
"metrics.pages": "Sivut",
|
||||
"metrics.referrers": "Viittaajat",
|
||||
"metrics.unique-visitors": "Uniikit vierailijat",
|
||||
"metrics.views": "Näyttökertoja",
|
||||
"metrics.unique-visitors": "Yksittäiset kävijät",
|
||||
"metrics.views": "Näyttökerrat",
|
||||
"metrics.visitors": "Vierailijat"
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Fyri at spora hagtøl fyri {target}, koyr kotuna í {head} partin á tínari heimasíðu.",
|
||||
"message.type-delete": "Skriva {delete} í feltið fyri at vátta",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Skriva {reset} í feltið fyri at vátta",
|
||||
"metrics.actions": "Gerðir",
|
||||
"metrics.average-visit-time": "Miðal vitjurnartíð ",
|
||||
"metrics.bounce-rate": "Bounce prosenttal",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "יש להוסיף את הקוד הבא לאזור ה-{head} של האתר",
|
||||
"message.type-delete": "הקלידו {delete} בתיבה על מנת לאשר",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "הקלידו {reset} בתיבה על מנת לאשר",
|
||||
"metrics.actions": "פעולות",
|
||||
"metrics.average-visit-time": "זמן ביקור ממוצע",
|
||||
"metrics.bounce-rate": "Bounce rate",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target} के आँकड़ों को ट्रैक करने के लिए, अपनी वेबसाइट के {head} अनुभाग में निम्नलिखित कोड रखें।",
|
||||
"message.type-delete": "पुष्टि करने के लिए नीचे दिए गए बॉक्स में {delete} टाइप करें।",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "ुष्टि करने के लिए नीचे दिए गए बॉक्स में {reset} टाइप करें।",
|
||||
"metrics.actions": "कार्य",
|
||||
"metrics.average-visit-time": "औसत दृश्य समय",
|
||||
"metrics.bounce-rate": "उछाल दर",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target} statisztikáinak nyomon követéséhez, helyezd el az alábbi kódot a weboldalad {head} részébe.",
|
||||
"message.type-delete": "Megerősítéshez írd be az alábbi mezőbe azt, hogy {delete}.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Megerősítéshez írd be az alábbi mezőbe azt, hogy {reset}.",
|
||||
"metrics.actions": "Műveletek",
|
||||
"metrics.average-visit-time": "Átlagos látogatási idő",
|
||||
"metrics.bounce-rate": "Visszafordulási arány",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Untuk melacak statistik {target}, tempatkan kode berikut di bagian {head} situs web anda.",
|
||||
"message.type-delete": "Ketikkan {delete} pada kotak di bawah untuk konfirmasi.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Ketikkan {reset} pada kotak di bawah untuk konfirmasi.",
|
||||
"metrics.actions": "Aksi",
|
||||
"metrics.average-visit-time": "Waktu kunjungan rata-rata",
|
||||
"metrics.bounce-rate": "Rasio pentalan",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target}のアクセス解析を開始するには、次のコードをWebサイトの{head}セクションへ追加してください。",
|
||||
"message.type-delete": "確認のため、下のフォームに{delete}と入力してください。",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "確認のため、下のフォームに{reset}と入力してください。",
|
||||
"metrics.actions": "アクション",
|
||||
"metrics.average-visit-time": "平均滞在時間",
|
||||
"metrics.bounce-rate": "直帰率",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target}에 대한 통계를 추적하려면 웹사이트의 {head} 섹션에 다음 코드를 입력하십시오.",
|
||||
"message.type-delete": "확인을 위해 아래 박스에 {delete}값을 입력하십시오.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "확인을 위해 아래 박스에 {reset}값을 입력하십시오.",
|
||||
"metrics.actions": "액션",
|
||||
"metrics.average-visit-time": "평균 방문 시간",
|
||||
"metrics.bounce-rate": "이탈률",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target} вебийн статистикийг бүртгэхийн тулд доорх кодыг вебийнхээ {head} хэсэгт байрлуулна уу.",
|
||||
"message.type-delete": "Доорх хэсэгт {delete} гэж бичиж баталгаажуулна уу.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Доорх хэсэгт {reset} гэж бичиж баталгаажуулна уу.",
|
||||
"metrics.actions": "Үйлдлүүд",
|
||||
"metrics.average-visit-time": "Зочилсон дундаж хугацаа",
|
||||
"metrics.bounce-rate": "Нэг хуудас үзээд гарсан",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Untuk menjejak statistik bagi {target}, letakkan kod berikut di bahagian {head} laman web anda.",
|
||||
"message.type-delete": "Taip {delete} di dalam kotak di bawah untuk pengesahan.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Taip {reset} di dalam kotak di bawah untuk pengesahan.",
|
||||
"metrics.actions": "Aksi",
|
||||
"metrics.average-visit-time": "Purata tempoh masa lawatan",
|
||||
"metrics.bounce-rate": "Kadar lantunan",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "For å spore statistikk for {target}, plasser følgende kode i {head}-delen av nettstedet ditt.",
|
||||
"message.type-delete": "Skriv inn {delete} i boksen nedenfor for å bekrefte.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Skriv inn {reset} i boksen nedenfor for å bekrefte.",
|
||||
"metrics.actions": "Handlinger",
|
||||
"metrics.average-visit-time": "Gjennomsnittlig besøkelsestid",
|
||||
"metrics.bounce-rate": "Avvisningsfrekvens",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Om statistieken voor {target} bij te houden, plaats je de volgende code in het {head} gedeelte van je website.",
|
||||
"message.type-delete": "Type {delete} in onderstaande veld om dit te bevestigen.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Type {reset} in onderstaande veld om dit te bevestigen.",
|
||||
"metrics.actions": "Acties",
|
||||
"metrics.average-visit-time": "Gemiddelde bezoektijd",
|
||||
"metrics.bounce-rate": "Bouncepercentage",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Aby śledzić statystyki dla {target}, umieść poniższy kod w sekcji {head} swojej witryny.",
|
||||
"message.type-delete": "Wpisz {delete} w polu poniżej, aby potwierdzić.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Wpisz {reset} w polu poniżej, aby potwierdzić.",
|
||||
"metrics.actions": "Działania",
|
||||
"metrics.average-visit-time": "Średni czas wizyty",
|
||||
"metrics.bounce-rate": "Współczynnik odrzuceń",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Para gerar estatística para {target}, coloque o seguinte código no {head} do html do seu site.",
|
||||
"message.type-delete": "Escreva {delete} abaixo para continuar.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Escreva {reset} abaixo para continuar.",
|
||||
"metrics.actions": "Ações",
|
||||
"metrics.average-visit-time": "Tempo médio da visita",
|
||||
"metrics.bounce-rate": "Taxa de rejeição",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Pre sledovanie návštev na {target}, pridajte následujúci kód do {head} časti vašeho webu.",
|
||||
"message.type-delete": "Napíšte {delete} pre potvrdenie.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Napíšte {reset} pre potvrdenie.",
|
||||
"metrics.actions": "Akcie",
|
||||
"metrics.average-visit-time": "Priemerný čas návštevy",
|
||||
"metrics.bounce-rate": "Okamžité opustenie",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Če želite spremljati statistične podatke za {target}, v {head} del vašega spletnega mesta namestite naslednjo kodo.",
|
||||
"message.type-delete": "V spodnje polje vnesite {delete} za potrditev.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "V spodnje polje vnesite {reset} za potrditev.",
|
||||
"metrics.actions": "Dejanja",
|
||||
"metrics.average-visit-time": "Povprečni čas obiska",
|
||||
"metrics.bounce-rate": "Zapustna stopnja",
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"label.add-website": "Lägg till webbsajt",
|
||||
"label.administrator": "Administratör",
|
||||
"label.all": "Alla",
|
||||
"label.all-events": "All events",
|
||||
"label.all-time": "All time",
|
||||
"label.all-events": "Alla händelser",
|
||||
"label.all-time": "Sedan början",
|
||||
"label.all-websites": "Alla sajter",
|
||||
"label.back": "Tillbaka",
|
||||
"label.cancel": "Avbryt",
|
||||
|
@ -28,7 +28,7 @@
|
|||
"label.enable-share-url": "Aktivera delnings-URL",
|
||||
"label.invalid": "Ogiltig",
|
||||
"label.invalid-domain": "Ogiltig domän",
|
||||
"label.language": "Language",
|
||||
"label.language": "Språk",
|
||||
"label.last-days": "Senaste {x} dagarna",
|
||||
"label.last-hours": "Senaste {x} timmarna",
|
||||
"label.logged-in-as": "Inloggad som {username}",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"label.more": "Mer",
|
||||
"label.name": "Namn",
|
||||
"label.new-password": "Nytt lösenord",
|
||||
"label.owner": "Owner",
|
||||
"label.owner": "Ägare",
|
||||
"label.password": "Lösenord",
|
||||
"label.passwords-dont-match": "Lösenorden är inte samma",
|
||||
"label.profile": "Profil",
|
||||
|
@ -46,12 +46,12 @@
|
|||
"label.refresh": "Uppdatera",
|
||||
"label.required": "Krävs",
|
||||
"label.reset": "Återställ",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Återställ statistik",
|
||||
"label.save": "Spara",
|
||||
"label.settings": "Inställningar",
|
||||
"label.share-url": "Delnings-URL",
|
||||
"label.single-day": "En dag",
|
||||
"label.theme": "Theme",
|
||||
"label.theme": "Tema",
|
||||
"label.this-month": "Denna månad",
|
||||
"label.this-week": "Denna vecka",
|
||||
"label.this-year": "Detta år",
|
||||
|
@ -64,27 +64,27 @@
|
|||
"label.websites": "Webbsajt",
|
||||
"message.active-users": "{x} {x, plural, one {besökare} other {besökare}} just nu",
|
||||
"message.confirm-delete": "Är du säker på att du vill radera {target}?",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.confirm-reset": "Är du säker på att du vill återställa statistiken för {target}?",
|
||||
"message.copied": "Kopierad!",
|
||||
"message.delete-warning": "All tillhörande data kommer också raderas.",
|
||||
"message.failure": "Något gick fel.",
|
||||
"message.get-share-url": "Visa delnings-URL",
|
||||
"message.get-tracking-code": "Visa spårningskod",
|
||||
"message.go-to-settings": "Gå till inställningar",
|
||||
"message.incorrect-username-password": "Felaktikt användarnamn/lösenord.",
|
||||
"message.incorrect-username-password": "Felaktigt användarnamn/lösenord.",
|
||||
"message.log.visitor": "Besökare från {country} med {browser} på {os} {device}",
|
||||
"message.new-version-available": "En ny version av umami {version} är tillgänglig!",
|
||||
"message.no-data-available": "Ingen data tillgänglig.",
|
||||
"message.no-websites-configured": "Du har inga webbsajter.",
|
||||
"message.page-not-found": "Sidan kan inte hittas.",
|
||||
"message.powered-by": "Drivs av {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "All statistik för webbsajten tas bort men spårningskoden förblir oförändrad.",
|
||||
"message.save-success": "Sparades!",
|
||||
"message.share-url": "Det här är den offentliga delnings-URL:en {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.share-url": "Det här är den offentliga delnings-URL:en för {target}.",
|
||||
"message.toggle-charts": "Visa/göm grafer",
|
||||
"message.track-stats": "För att spåra statistik för {target}, placera följande kod i {head}-avsnittet på din webbsajt.",
|
||||
"message.type-delete": "Skriv {delete} i rutan nedan för att radera.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Skriv {reset} i rutan nedan för att bekräfta.",
|
||||
"metrics.actions": "Händelser",
|
||||
"metrics.average-visit-time": "Medelbesökstid",
|
||||
"metrics.bounce-rate": "Avvisningfrekvens",
|
||||
|
@ -99,7 +99,7 @@
|
|||
"metrics.filter.combined": "Kombinerade",
|
||||
"metrics.filter.domain-only": "Endast domän",
|
||||
"metrics.filter.raw": "Rådata",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.languages": "Språk",
|
||||
"metrics.operating-systems": "Operativsystem",
|
||||
"metrics.page-views": "Sidvisningar",
|
||||
"metrics.pages": "Sidor",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target}க்கான புள்ளிவிவரங்களைக் கண்காணிக்க, {head}ல் பின்வரும் குறியீட்டை வைக்கவும்.",
|
||||
"message.type-delete": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் {delete} என தட்டச்சு செய்க.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் {reset} என தட்டச்சு செய்க.",
|
||||
"metrics.actions": "செயல்கள்",
|
||||
"metrics.average-visit-time": "சராசரி வருகை நேரம்",
|
||||
"metrics.bounce-rate": "துள்ளல் விகிதம்",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "{target} alanı adı istatistiklerini takip etmek için, aşağıdaki kodu web sitenizin {head} bloğuna yerleştirin.",
|
||||
"message.type-delete": "Onaylamak için kutuya {delete} yazın.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Onaylamak için kutuya {reset} yazın.",
|
||||
"metrics.actions": "Hareketler",
|
||||
"metrics.average-visit-time": "Ortalama ziyaret süresi",
|
||||
"metrics.bounce-rate": "Çıkma oranı",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "Để theo dõi {target}, dán mã theo dõi vào {head} của website bạn.",
|
||||
"message.type-delete": "Nhập {delete} bên dưới để xác nhận.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Nhập {reset} bên dưới để xác nhận.",
|
||||
"metrics.actions": "Hành động",
|
||||
"metrics.average-visit-time": "Thời gian truy cập trung bình",
|
||||
"metrics.bounce-rate": "Tỷ lệ thoát trang",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "把以下代码放到你的网站的 {head} 部分来收集 {target} 的数据。",
|
||||
"message.type-delete": "在下方输入框输入 {delete} 以确认删除。",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "在下方输入框输入 {reset} 以确认删除。",
|
||||
"metrics.actions": "用户行为",
|
||||
"metrics.average-visit-time": "平均访问时间",
|
||||
"metrics.bounce-rate": "跳出率",
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
"message.toggle-charts": "Toggle charts",
|
||||
"message.track-stats": "將以下代碼放入被設定網站的 {head} 部分來收集 {target} 的資料。",
|
||||
"message.type-delete": "在下方空格輸入 {delete} 確認",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "在下方空格輸入 {reset} 確認",
|
||||
"metrics.actions": "用戶行為",
|
||||
"metrics.average-visit-time": "平均訪問時間",
|
||||
"metrics.bounce-rate": "跳出率",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BROWSERS } from './constants';
|
||||
import { removeTrailingSlash, removeWWW, getDomainName } from './url';
|
||||
import { removeTrailingSlash, removeWWW } from './url';
|
||||
|
||||
export const urlFilter = (data, { raw }) => {
|
||||
const isValidUrl = url => {
|
||||
|
@ -46,23 +46,18 @@ export const urlFilter = (data, { raw }) => {
|
|||
};
|
||||
|
||||
export const refFilter = (data, { domain, domainOnly, raw }) => {
|
||||
const domainName = getDomainName(domain);
|
||||
const regex = new RegExp(`http[s]?://${domainName}`);
|
||||
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domain}`);
|
||||
const links = {};
|
||||
|
||||
const isValidRef = ref => {
|
||||
return ref !== '' && ref !== null && !ref.startsWith('/') && !ref.startsWith('#');
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
|
||||
}
|
||||
|
||||
const cleanUrl = url => {
|
||||
try {
|
||||
const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
|
||||
|
||||
if (hostname === domainName) {
|
||||
if (regex.test(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -88,6 +83,10 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
|
|||
}
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
|
||||
}
|
||||
|
||||
const map = data.reduce((obj, { x, y }) => {
|
||||
if (!isValidRef(x)) {
|
||||
return obj;
|
||||
|
|
|
@ -86,3 +86,11 @@ export async function getClientInfo(req, { screen }) {
|
|||
|
||||
return { userAgent, browser, os, ip, country, device };
|
||||
}
|
||||
|
||||
export function getJsonBody(req) {
|
||||
if ((req.headers['content-type'] || '').indexOf('text/plain') !== -1) {
|
||||
return JSON.parse(req.body);
|
||||
}
|
||||
|
||||
return req.body;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
export function ok(res, data = {}) {
|
||||
return json(res, data);
|
||||
}
|
||||
|
||||
export function json(res, data = {}) {
|
||||
return res.status(200).json(data);
|
||||
}
|
||||
|
||||
export function send(res, data) {
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
export function redirect(res, url) {
|
||||
res.setHeader('Location', url);
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries';
|
||||
import { getClientInfo } from 'lib/request';
|
||||
import { getJsonBody, getClientInfo } from 'lib/request';
|
||||
import { uuid, isValidUuid, parseToken } from 'lib/crypto';
|
||||
|
||||
export async function getSession(req) {
|
||||
const { payload } = req.body;
|
||||
const { payload } = getJsonBody(req);
|
||||
|
||||
if (!payload) {
|
||||
throw new Error('Invalid request');
|
||||
|
@ -32,7 +32,7 @@ export async function getSession(req) {
|
|||
}
|
||||
|
||||
const { website_id } = website;
|
||||
const session_uuid = uuid(website_id, hostname, ip, userAgent, os);
|
||||
const session_uuid = uuid(website_id, hostname, ip, userAgent);
|
||||
|
||||
let session = await getSessionByUuid(session_uuid);
|
||||
|
||||
|
|
36
package.json
36
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "umami",
|
||||
"version": "1.27.0",
|
||||
"version": "1.28.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics.",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
|
@ -21,10 +21,11 @@
|
|||
"build-geo": "node scripts/build-geo.js",
|
||||
"build-db-schema": "dotenv prisma introspect",
|
||||
"build-db-client": "dotenv prisma generate",
|
||||
"build-mysql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.mysql.prisma",
|
||||
"build-mysql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.mysql.prisma",
|
||||
"build-mysql-client": "dotenv prisma generate -- --schema=./prisma/schema.mysql.prisma",
|
||||
"build-postgresql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.postgresql.prisma",
|
||||
"build-postgresql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.postgresql.prisma",
|
||||
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
|
||||
"postbuild": "node scripts/postbuild.js",
|
||||
"copy-db-schema": "node scripts/copy-db-schema.js",
|
||||
"generate-lang": "npm-run-all extract-lang merge-lang",
|
||||
"extract-lang": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json",
|
||||
|
@ -52,8 +53,9 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "4.5.4",
|
||||
"@prisma/client": "3.9.2",
|
||||
"@fontsource/inter": "4.5.5",
|
||||
"@prisma/client": "3.11.0",
|
||||
"async-retry": "^1.3.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chart.js": "^2.9.4",
|
||||
|
@ -65,21 +67,25 @@
|
|||
"detect-browser": "^5.2.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"formik": "^2.2.9",
|
||||
"fs-extra": "^10.0.1",
|
||||
"immer": "^9.0.12",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"is-docker": "^3.0.0",
|
||||
"is-localhost-ip": "^1.4.0",
|
||||
"isbot": "^3.2.2",
|
||||
"isbot": "^3.4.5",
|
||||
"jose": "2.0.5",
|
||||
"maxmind": "^4.3.2",
|
||||
"maxmind": "^4.3.6",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"next": "12.1.0",
|
||||
"node-fetch": "^3.2.3",
|
||||
"prompts": "2.4.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-intl": "^5.20.6",
|
||||
"react-intl": "^5.24.7",
|
||||
"react-simple-maps": "^2.3.0",
|
||||
"react-spring": "^9.4.3",
|
||||
"react-spring": "^9.4.4",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"react-use-measure": "^2.0.4",
|
||||
"react-window": "^1.8.6",
|
||||
|
@ -101,21 +107,21 @@
|
|||
"eslint-config-next": "^12.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^7.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.2.15",
|
||||
"postcss": "^8.4.12",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-preset-env": "^7.4.2",
|
||||
"postcss-rtlcss": "^3.3.2",
|
||||
"prettier": "^2.3.2",
|
||||
"postcss-rtlcss": "^3.5.3",
|
||||
"prettier": "^2.6.0",
|
||||
"prettier-eslint": "^13.0.0",
|
||||
"prisma": "3.9.2",
|
||||
"rollup": "^2.69.0",
|
||||
"prisma": "3.11.0",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"stylelint": "^14.5.3",
|
||||
"stylelint-config-css-modules": "^3.0.0",
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
|
||||
function redirectHTTPS(req) {
|
||||
const host = req.headers.get('host');
|
||||
if (
|
||||
process.env.FORCE_SSL &&
|
||||
!req.headers.get('host').includes('localhost') &&
|
||||
req.nextUrl.protocol !== 'https'
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
req.nextUrl.protocol === 'http:'
|
||||
) {
|
||||
return NextResponse.redirect(`https://${req.headers.get('host')}${req.nextUrl.pathname}`, 301);
|
||||
return NextResponse.redirect(`https://${host}${req.nextUrl.pathname}`, 301);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import isbot from 'isbot';
|
|||
import ipaddr from 'ipaddr.js';
|
||||
import { savePageView, saveEvent } from 'lib/queries';
|
||||
import { useCors, useSession } from 'lib/middleware';
|
||||
import { getIpAddress } from 'lib/request';
|
||||
import { ok, badRequest } from 'lib/response';
|
||||
import { getJsonBody, getIpAddress } from 'lib/request';
|
||||
import { ok, send, badRequest } from 'lib/response';
|
||||
import { createToken } from 'lib/crypto';
|
||||
import { removeTrailingSlash } from 'lib/url';
|
||||
|
||||
|
@ -39,10 +39,11 @@ export default async (req, res) => {
|
|||
await useSession(req, res);
|
||||
|
||||
const {
|
||||
body: { type, payload },
|
||||
session: { website_id, session_id },
|
||||
} = req;
|
||||
|
||||
const { type, payload } = getJsonBody(req);
|
||||
|
||||
let { url, referrer, event_type, event_value } = payload;
|
||||
|
||||
if (process.env.REMOVE_TRAILING_SLASH) {
|
||||
|
@ -59,5 +60,5 @@ export default async (req, res) => {
|
|||
|
||||
const token = await createToken({ website_id, session_id });
|
||||
|
||||
return ok(res, token);
|
||||
return send(res, token);
|
||||
};
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Layout from 'components/layout/Layout';
|
||||
import WebsiteList from 'components/pages/WebsiteList';
|
||||
import Dashboard from 'components/pages/Dashboard';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { loading } = useRequireLogin();
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
const userId = id?.[0];
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
|
@ -16,7 +12,7 @@ export default function DashboardPage() {
|
|||
|
||||
return (
|
||||
<Layout>
|
||||
<WebsiteList userId={userId} />
|
||||
<Dashboard />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "اكتب "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " في الحقل التالي لتأكيد الحذف."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Napište "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " pro potvrzení."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Skriv "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " i boksen nedenfor, for at bekræfte."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Πληκτρολογήστε "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " στο παρακάτω πλαίσιο για επιβεβαίωση."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -640,15 +640,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"value": "reset"
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": "جهت اطمینان {reset} را در کادر زیر بنویسید."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
"label.all-events": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All events"
|
||||
"value": "Kaikki tapahtumat"
|
||||
}
|
||||
],
|
||||
"label.all-time": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All time"
|
||||
"value": "Alusta lähtien"
|
||||
}
|
||||
],
|
||||
"label.all-websites": [
|
||||
|
@ -86,7 +86,7 @@
|
|||
"label.custom-range": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Mukautettu jakso"
|
||||
"value": "Mukautettu ajanjakso"
|
||||
}
|
||||
],
|
||||
"label.dashboard": [
|
||||
|
@ -176,7 +176,7 @@
|
|||
"label.language": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Language"
|
||||
"value": "Kieli"
|
||||
}
|
||||
],
|
||||
"label.last-days": [
|
||||
|
@ -190,7 +190,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " päivät"
|
||||
"value": " päivää"
|
||||
}
|
||||
],
|
||||
"label.last-hours": [
|
||||
|
@ -204,7 +204,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " tunnit"
|
||||
"value": " tuntia"
|
||||
}
|
||||
],
|
||||
"label.logged-in-as": [
|
||||
|
@ -250,7 +250,7 @@
|
|||
"label.owner": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Owner"
|
||||
"value": "Omistaja"
|
||||
}
|
||||
],
|
||||
"label.password": [
|
||||
|
@ -274,7 +274,7 @@
|
|||
"label.realtime": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Reaaliaikainen"
|
||||
"value": "Juuri nyt"
|
||||
}
|
||||
],
|
||||
"label.realtime-logs": [
|
||||
|
@ -304,7 +304,7 @@
|
|||
"label.reset-website": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Reset statistics"
|
||||
"value": "Nollaa tilastot"
|
||||
}
|
||||
],
|
||||
"label.save": [
|
||||
|
@ -334,7 +334,7 @@
|
|||
"label.theme": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Theme"
|
||||
"value": "Teema"
|
||||
}
|
||||
],
|
||||
"label.this-month": [
|
||||
|
@ -404,7 +404,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " nykyinen "
|
||||
"value": " "
|
||||
},
|
||||
{
|
||||
"offset": 0,
|
||||
|
@ -413,7 +413,7 @@
|
|||
"value": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "yksi"
|
||||
"value": "vierailija"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -421,7 +421,7 @@
|
|||
"value": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "muut"
|
||||
"value": "vierailijaa"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -434,7 +434,7 @@
|
|||
"message.confirm-delete": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Haluatko varmasti poistaa "
|
||||
"value": "Haluatko varmasti poistaa sivuston "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -448,7 +448,7 @@
|
|||
"message.confirm-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Are your sure you want to reset "
|
||||
"value": "Haluatko varmasti poistaa sivuston "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -456,7 +456,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": "'s statistics?"
|
||||
"value": " tilastot?"
|
||||
}
|
||||
],
|
||||
"message.copied": [
|
||||
|
@ -474,7 +474,7 @@
|
|||
"message.failure": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Jotain meni väärin."
|
||||
"value": "Jotain meni pieleen."
|
||||
}
|
||||
],
|
||||
"message.get-share-url": [
|
||||
|
@ -512,7 +512,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " käyttäen selainta "
|
||||
"value": " selaimella "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -520,7 +520,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " "
|
||||
"value": " laitteella "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -528,7 +528,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": "-laitteella: "
|
||||
"value": " "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -580,7 +580,7 @@
|
|||
"message.reset-warning": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All statistics for this website will be deleted, but your tracking code will remain intact."
|
||||
"value": "Kaikki sivuston tilastot poistetaan, mutta seurantakoodi pysyy muuttumattomana."
|
||||
}
|
||||
],
|
||||
"message.save-success": [
|
||||
|
@ -592,7 +592,7 @@
|
|||
"message.share-url": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Tämä on julkisesti jaettu URL-osoitteelle "
|
||||
"value": "Tämä on julkisesti jaettu URL sivustolle "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -606,13 +606,13 @@
|
|||
"message.toggle-charts": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Toggle charts"
|
||||
"value": "Kytke kaaviot päälle/pois"
|
||||
}
|
||||
],
|
||||
"message.track-stats": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Jos haluat seurata kohteen "
|
||||
"value": "Jos haluat seurata sivuston "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -628,7 +628,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " osioon."
|
||||
"value": "-osioon."
|
||||
}
|
||||
],
|
||||
"message.type-delete": [
|
||||
|
@ -642,13 +642,13 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " alla olevaan ruutuun vahvistaaksesi."
|
||||
"value": " alla olevaan kenttään vahvistaaksesi."
|
||||
}
|
||||
],
|
||||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Kirjoita "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " alla olevaan kenttään vahvistaaksesi."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
@ -704,7 +704,7 @@
|
|||
"metrics.device.mobile": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Mobiili"
|
||||
"value": "Puhelin"
|
||||
}
|
||||
],
|
||||
"metrics.device.tablet": [
|
||||
|
@ -746,7 +746,7 @@
|
|||
"metrics.languages": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Languages"
|
||||
"value": "Kielet"
|
||||
}
|
||||
],
|
||||
"metrics.operating-systems": [
|
||||
|
@ -758,7 +758,7 @@
|
|||
"metrics.page-views": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Sivun näyttökertoja"
|
||||
"value": "Sivun näyttökerrat"
|
||||
}
|
||||
],
|
||||
"metrics.pages": [
|
||||
|
@ -776,13 +776,13 @@
|
|||
"metrics.unique-visitors": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Uniikit vierailijat"
|
||||
"value": "Yksittäiset kävijät"
|
||||
}
|
||||
],
|
||||
"metrics.views": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Näyttökertoja"
|
||||
"value": "Näyttökerrat"
|
||||
}
|
||||
],
|
||||
"metrics.visitors": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Skriva "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " í feltið fyri at vátta"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -628,7 +628,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "הקלידו "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -636,7 +636,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " בתיבה על מנת לאשר"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -636,7 +636,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "ुष्टि करने के लिए नीचे दिए गए बॉक्स में "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -644,7 +644,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " टाइप करें।"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Megerősítéshez írd be az alábbi mezőbe azt, hogy "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": "."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -616,7 +616,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Ketikkan "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -624,7 +624,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " pada kotak di bawah untuk konfirmasi."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -620,7 +620,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "確認のため、下のフォームに"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -628,7 +628,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": "と入力してください。"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -620,7 +620,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "확인을 위해 아래 박스에 "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -628,7 +628,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": "값을 입력하십시오."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Доорх хэсэгт "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " гэж бичиж баталгаажуулна уу."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -640,7 +640,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Taip "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -648,7 +648,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " di dalam kotak di bawah untuk pengesahan."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -652,7 +652,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Skriv inn "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -660,7 +660,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " i boksen nedenfor for å bekrefte."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " in onderstaande veld om dit te bevestigen."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Wpisz "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " w polu poniżej, aby potwierdzić."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -652,7 +652,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Escreva "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -660,7 +660,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " abaixo para continuar."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Napíšte "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " pre potvrdenie."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -648,7 +648,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "V spodnje polje vnesite "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -656,7 +656,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " za potrditev."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
"label.all-events": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All events"
|
||||
"value": "Alla händelser"
|
||||
}
|
||||
],
|
||||
"label.all-time": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All time"
|
||||
"value": "Sedan början"
|
||||
}
|
||||
],
|
||||
"label.all-websites": [
|
||||
|
@ -176,7 +176,7 @@
|
|||
"label.language": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Language"
|
||||
"value": "Språk"
|
||||
}
|
||||
],
|
||||
"label.last-days": [
|
||||
|
@ -250,7 +250,7 @@
|
|||
"label.owner": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Owner"
|
||||
"value": "Ägare"
|
||||
}
|
||||
],
|
||||
"label.password": [
|
||||
|
@ -304,7 +304,7 @@
|
|||
"label.reset-website": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Reset statistics"
|
||||
"value": "Återställ statistik"
|
||||
}
|
||||
],
|
||||
"label.save": [
|
||||
|
@ -334,7 +334,7 @@
|
|||
"label.theme": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Theme"
|
||||
"value": "Tema"
|
||||
}
|
||||
],
|
||||
"label.this-month": [
|
||||
|
@ -452,7 +452,7 @@
|
|||
"message.confirm-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Are your sure you want to reset "
|
||||
"value": "Är du säker på att du vill återställa statistiken för "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -460,7 +460,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": "'s statistics?"
|
||||
"value": "?"
|
||||
}
|
||||
],
|
||||
"message.copied": [
|
||||
|
@ -502,7 +502,7 @@
|
|||
"message.incorrect-username-password": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Felaktikt användarnamn/lösenord."
|
||||
"value": "Felaktigt användarnamn/lösenord."
|
||||
}
|
||||
],
|
||||
"message.log.visitor": [
|
||||
|
@ -584,7 +584,7 @@
|
|||
"message.reset-warning": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "All statistics for this website will be deleted, but your tracking code will remain intact."
|
||||
"value": "All statistik för webbsajten tas bort men spårningskoden förblir oförändrad."
|
||||
}
|
||||
],
|
||||
"message.save-success": [
|
||||
|
@ -596,7 +596,7 @@
|
|||
"message.share-url": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Det här är den offentliga delnings-URL:en "
|
||||
"value": "Det här är den offentliga delnings-URL:en för "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -610,7 +610,7 @@
|
|||
"message.toggle-charts": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Toggle charts"
|
||||
"value": "Visa/göm grafer"
|
||||
}
|
||||
],
|
||||
"message.track-stats": [
|
||||
|
@ -652,7 +652,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Skriv "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -660,7 +660,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " i rutan nedan för att bekräfta."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
@ -750,7 +750,7 @@
|
|||
"metrics.languages": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Languages"
|
||||
"value": "Språk"
|
||||
}
|
||||
],
|
||||
"metrics.operating-systems": [
|
||||
|
|
|
@ -640,7 +640,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -648,7 +648,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " என தட்டச்சு செய்க."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -612,7 +612,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Onaylamak için kutuya "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -620,7 +620,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " yazın."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -632,7 +632,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "Nhập "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -640,7 +640,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " bên dưới để xác nhận."
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -636,7 +636,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "在下方输入框输入 "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -644,7 +644,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " 以确认删除。"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -632,7 +632,7 @@
|
|||
"message.type-reset": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Type "
|
||||
"value": "在下方空格輸入 "
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
@ -640,7 +640,7 @@
|
|||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " in the box below to confirm."
|
||||
"value": " 確認"
|
||||
}
|
||||
],
|
||||
"metrics.actions": [
|
||||
|
|
|
@ -23,7 +23,7 @@ files.forEach(file => {
|
|||
keys.forEach(key => {
|
||||
const orig = messages[key];
|
||||
const check = lang[key];
|
||||
const ignored = ignore[id]?.includes(key);
|
||||
const ignored = ignore[id] === '*' || ignore[id]?.includes(key);
|
||||
|
||||
if (!ignored && (!check || check === orig)) {
|
||||
console.log(chalk.redBright('*'), chalk.greenBright(`${key}:`), orig);
|
||||
|
@ -32,7 +32,7 @@ files.forEach(file => {
|
|||
});
|
||||
|
||||
if (count === 0) {
|
||||
console.log('**👍 Complete!**');
|
||||
console.log('**Complete!**');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const fs = require('fs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const chalk = require('chalk');
|
||||
|
@ -16,11 +16,9 @@ const asyncForEach = async (array, callback) => {
|
|||
}
|
||||
};
|
||||
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.mkdirSync(dest);
|
||||
}
|
||||
|
||||
const download = async files => {
|
||||
await fs.ensureDir(dest);
|
||||
|
||||
await asyncForEach(files, async file => {
|
||||
const locale = file.replace('-', '_').replace('.json', '');
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const fs = require('fs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const chalk = require('chalk');
|
||||
|
@ -16,11 +16,9 @@ const asyncForEach = async (array, callback) => {
|
|||
}
|
||||
};
|
||||
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.mkdirSync(dest);
|
||||
}
|
||||
|
||||
const download = async files => {
|
||||
await fs.ensureDir(dest);
|
||||
|
||||
await asyncForEach(files, async file => {
|
||||
const locale = file.replace('-', '_').replace('.json', '');
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const fs = require('fs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const del = require('del');
|
||||
const prettier = require('prettier');
|
||||
|
@ -14,22 +14,24 @@ if (removed.length) {
|
|||
console.log(removed.map(n => `${n} ${chalk.redBright('✗')}`).join('\n'));
|
||||
}
|
||||
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.mkdirSync(dest);
|
||||
async function run() {
|
||||
await fs.ensureDir(dest);
|
||||
|
||||
files.forEach(file => {
|
||||
const lang = require(`../lang/${file}`);
|
||||
const keys = Object.keys(lang).sort();
|
||||
|
||||
const formatted = keys.reduce((obj, key) => {
|
||||
obj[key] = { defaultMessage: lang[key] };
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const json = prettier.format(JSON.stringify(formatted), { parser: 'json' });
|
||||
|
||||
fs.writeFileSync(path.resolve(dest, file), json);
|
||||
|
||||
console.log(path.resolve(src, file), chalk.greenBright('->'), path.resolve(dest, file));
|
||||
});
|
||||
}
|
||||
|
||||
files.forEach(file => {
|
||||
const lang = require(`../lang/${file}`);
|
||||
const keys = Object.keys(lang).sort();
|
||||
|
||||
const formatted = keys.reduce((obj, key) => {
|
||||
obj[key] = { defaultMessage: lang[key] };
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const json = prettier.format(JSON.stringify(formatted), { parser: 'json' });
|
||||
|
||||
fs.writeFileSync(path.resolve(dest, file), json);
|
||||
|
||||
console.log(path.resolve(src, file), chalk.greenBright('->'), path.resolve(dest, file));
|
||||
});
|
||||
run();
|
||||
|
|
10
scripts/postbuild.js
Normal file
10
scripts/postbuild.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
require('dotenv').config();
|
||||
const sendTelemetry = require('./telemetry');
|
||||
|
||||
async function run() {
|
||||
if (!process.env.TELEMETRY_DISABLE) {
|
||||
await sendTelemetry();
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
54
scripts/telemetry.js
Normal file
54
scripts/telemetry.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const retry = require('async-retry');
|
||||
const isCI = require('is-ci');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
const dest = path.resolve(__dirname, '../.next/cache/umami.json');
|
||||
const url = 'https://telemetry.umami.is/api/collect';
|
||||
|
||||
async function sendTelemetry() {
|
||||
await fs.ensureFile(dest);
|
||||
|
||||
let json = {};
|
||||
|
||||
try {
|
||||
json = await fs.readJSON(dest);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (json.version !== pkg.version) {
|
||||
const { default: isDocker } = await import('is-docker');
|
||||
const { default: fetch } = await import('node-fetch');
|
||||
|
||||
await fs.writeJSON(dest, { version: pkg.version });
|
||||
|
||||
const payload = {
|
||||
umami: pkg.version,
|
||||
node: process.version,
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
os: `${os.type()} (${os.version()})`,
|
||||
isDocker: isDocker(),
|
||||
isCI,
|
||||
};
|
||||
|
||||
await retry(
|
||||
async () => {
|
||||
await fetch(url, {
|
||||
method: 'post',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
},
|
||||
{ minTimeout: 500, retries: 1, factor: 1 },
|
||||
).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sendTelemetry;
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from 'lib/constants';
|
||||
import { getItem } from 'lib/web';
|
||||
|
||||
const defaultDashboardConfig = {
|
||||
export const defaultDashboardConfig = {
|
||||
showCharts: true,
|
||||
limit: DEFAULT_WEBSITE_LIMIT,
|
||||
};
|
||||
|
|
101
tracker/index.js
101
tracker/index.js
|
@ -22,6 +22,7 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
const autoTrack = attr('data-auto-track') !== 'false';
|
||||
const dnt = attr('data-do-not-track');
|
||||
const useCache = attr('data-cache');
|
||||
const cssEvents = attr('data-css-events') !== 'false';
|
||||
const domain = attr('data-domains') || '';
|
||||
const domains = domain.split(',').map(n => n.trim());
|
||||
|
||||
|
@ -29,7 +30,7 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
const eventSelect = "[class*='umami--']";
|
||||
const cacheKey = 'umami.cache';
|
||||
|
||||
const disableTracking = () =>
|
||||
const trackingDisabled = () =>
|
||||
(localStorage && localStorage.getItem('umami.disabled')) ||
|
||||
(dnt && doNotTrack()) ||
|
||||
(domain && !domains.includes(hostname));
|
||||
|
@ -47,7 +48,7 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
const post = (url, data, callback) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('POST', url, true);
|
||||
req.setRequestHeader('Content-Type', 'application/json');
|
||||
req.setRequestHeader('Content-Type', 'text/plain');
|
||||
|
||||
req.onreadystatechange = () => {
|
||||
if (req.readyState === 4) {
|
||||
|
@ -58,20 +59,24 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
const collect = (type, params, uuid) => {
|
||||
if (disableTracking()) return;
|
||||
const getPayload = () => ({
|
||||
website,
|
||||
hostname,
|
||||
screen,
|
||||
language,
|
||||
cache: useCache && sessionStorage.getItem(cacheKey),
|
||||
url: currentUrl,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
website: uuid,
|
||||
hostname,
|
||||
screen,
|
||||
language,
|
||||
cache: useCache && sessionStorage.getItem(cacheKey),
|
||||
};
|
||||
|
||||
Object.keys(params).forEach(key => {
|
||||
payload[key] = params[key];
|
||||
const assign = (a, b) => {
|
||||
Object.keys(b).forEach(key => {
|
||||
a[key] = b[key];
|
||||
});
|
||||
return a;
|
||||
};
|
||||
|
||||
const collect = (type, payload) => {
|
||||
if (trackingDisabled()) return;
|
||||
|
||||
post(
|
||||
`${root}/api/collect`,
|
||||
|
@ -86,28 +91,42 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
const trackView = (url = currentUrl, referrer = currentRef, uuid = website) => {
|
||||
collect(
|
||||
'pageview',
|
||||
{
|
||||
assign(getPayload(), {
|
||||
website: uuid,
|
||||
url,
|
||||
referrer,
|
||||
},
|
||||
uuid,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const trackEvent = (event_value, event_type = 'custom', url = currentUrl, uuid = website) => {
|
||||
collect(
|
||||
'event',
|
||||
{
|
||||
assign(getPayload(), {
|
||||
website: uuid,
|
||||
url,
|
||||
event_type,
|
||||
event_value,
|
||||
url,
|
||||
},
|
||||
uuid,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/* Handle events */
|
||||
|
||||
const sendEvent = (value, type) => {
|
||||
const payload = getPayload();
|
||||
|
||||
payload.event_type = type;
|
||||
payload.event_value = value;
|
||||
|
||||
const data = JSON.stringify({
|
||||
type: 'event',
|
||||
payload,
|
||||
});
|
||||
|
||||
navigator.sendBeacon(`${root}/api/collect`, data);
|
||||
};
|
||||
|
||||
const addEvents = node => {
|
||||
const elements = node.querySelectorAll(eventSelect);
|
||||
Array.prototype.forEach.call(elements, addEvent);
|
||||
|
@ -120,20 +139,18 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
const [, type, value] = className.split('--');
|
||||
const listener = listeners[className]
|
||||
? listeners[className]
|
||||
: (listeners[className] = () => trackEvent(value, type));
|
||||
: (listeners[className] = () => {
|
||||
if (element.tagName === 'A') {
|
||||
sendEvent(value, type);
|
||||
} else {
|
||||
trackEvent(value, type);
|
||||
}
|
||||
});
|
||||
|
||||
element.addEventListener(type, listener, true);
|
||||
});
|
||||
};
|
||||
|
||||
const monitorMutate = mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
const element = mutation.target;
|
||||
addEvent(element);
|
||||
addEvents(element);
|
||||
});
|
||||
};
|
||||
|
||||
/* Handle history changes */
|
||||
|
||||
const handlePush = (state, title, url) => {
|
||||
|
@ -153,6 +170,19 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
}
|
||||
};
|
||||
|
||||
const observeDocument = () => {
|
||||
const monitorMutate = mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
const element = mutation.target;
|
||||
addEvent(element);
|
||||
addEvents(element);
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(monitorMutate);
|
||||
observer.observe(document, { childList: true, subtree: true });
|
||||
};
|
||||
|
||||
/* Global */
|
||||
|
||||
if (!window.umami) {
|
||||
|
@ -165,20 +195,23 @@ import { removeTrailingSlash } from '../lib/url';
|
|||
|
||||
/* Start */
|
||||
|
||||
if (autoTrack && !disableTracking()) {
|
||||
if (autoTrack && !trackingDisabled()) {
|
||||
history.pushState = hook(history, 'pushState', handlePush);
|
||||
history.replaceState = hook(history, 'replaceState', handlePush);
|
||||
|
||||
const update = () => {
|
||||
if (document.readyState === 'complete') {
|
||||
addEvents(document);
|
||||
trackView();
|
||||
|
||||
const observer = new MutationObserver(monitorMutate);
|
||||
observer.observe(document, { childList: true, subtree: true });
|
||||
if (cssEvents) {
|
||||
addEvents(document);
|
||||
observeDocument();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('readystatechange', update, true);
|
||||
|
||||
update();
|
||||
}
|
||||
})(window);
|
||||
|
|
Loading…
Reference in New Issue
Block a user