- {!data ? (
-
- ) : (
+ {!data && loading &&
}
+ {error &&
}
+ {data && !error && (
<>
}
diff --git a/components/metrics/MetricsBar.module.css b/components/metrics/MetricsBar.module.css
index 4546f893..b13e974f 100644
--- a/components/metrics/MetricsBar.module.css
+++ b/components/metrics/MetricsBar.module.css
@@ -1,6 +1,7 @@
.bar {
display: flex;
cursor: pointer;
+ min-height: 80px;
}
.bar > div + div {
diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js
index 3ac8a395..16f61836 100644
--- a/components/metrics/MetricsTable.js
+++ b/components/metrics/MetricsTable.js
@@ -1,33 +1,31 @@
-import React, { useState, useMemo } from 'react';
+import React, { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
-import { FixedSizeList } from 'react-window';
-import { useSpring, animated, config } from 'react-spring';
import classNames from 'classnames';
import Link from 'components/common/Link';
import Loading from 'components/common/Loading';
-import NoData from 'components/common/NoData';
import useFetch from 'hooks/useFetch';
import Arrow from 'assets/arrow-right.svg';
import { percentFilter } from 'lib/filters';
-import { formatNumber, formatLongNumber } from 'lib/format';
import useDateRange from 'hooks/useDateRange';
import usePageQuery from 'hooks/usePageQuery';
+import useShareToken from 'hooks/useShareToken';
+import ErrorMessage from 'components/common/ErrorMessage';
+import DataTable from './DataTable';
+import { DEFAULT_ANIMATION_DURATION, TOKEN_HEADER } from 'lib/constants';
import styles from './MetricsTable.module.css';
export default function MetricsTable({
websiteId,
websiteDomain,
- token,
- title,
- metric,
type,
className,
dataFilter,
filterOptions,
limit,
- renderLabel,
- onDataLoad = () => {},
+ onDataLoad,
+ ...props
}) {
+ const shareToken = useShareToken();
const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, modified } = dateRange;
const {
@@ -36,23 +34,24 @@ export default function MetricsTable({
query: { url },
} = usePageQuery();
- const { data } = useFetch(
- `/api/website/${websiteId}/rankings`,
+ const { data, loading, error } = useFetch(
+ `/api/website/${websiteId}/metrics`,
{
- type,
- start_at: +startDate,
- end_at: +endDate,
- domain: websiteDomain,
- url,
- token,
+ params: {
+ type,
+ start_at: +startDate,
+ end_at: +endDate,
+ domain: websiteDomain,
+ url,
+ },
+ onDataLoad,
+ delay: DEFAULT_ANIMATION_DURATION,
+ headers: { [TOKEN_HEADER]: shareToken?.token },
},
- { onDataLoad, delay: 300, update: [modified] },
+ [modified],
);
- const [format, setFormat] = useState(true);
- const formatFunc = format ? formatLongNumber : formatNumber;
- const shouldAnimate = limit > 0;
- const rankings = useMemo(() => {
+ const filteredData = useMemo(() => {
if (data) {
const items = percentFilter(dataFilter ? dataFilter(data, filterOptions) : data);
if (limit) {
@@ -61,92 +60,26 @@ export default function MetricsTable({
return items;
}
return [];
- }, [data, dataFilter, filterOptions]);
-
- const handleSetFormat = () => setFormat(state => !state);
-
- const getRow = row => {
- const { x: label, y: value, z: percent } = row;
- return (
-
- );
- };
-
- const Row = ({ index, style }) => {
- return
{getRow(rankings[index])}
;
- };
+ }, [data, error, dataFilter, filterOptions]);
return (
- {!data &&
}
- {data && (
- <>
-
-
{title}
-
- {metric}
-
-
-
- {rankings?.length === 0 && }
- {limit
- ? rankings.map(row => getRow(row))
- : rankings.length > 0 && (
-
- {Row}
-
- )}
-
-
- {limit && (
- }
- href={router.pathname}
- as={resolve({ view: type })}
- size="small"
- iconRight
- >
-
-
- )}
-
- >
- )}
+ {!data && loading &&
}
+ {error &&
}
+ {data && !error &&
}
+
+ {data && !error && limit && (
+ }
+ href={router.pathname}
+ as={resolve({ view: type })}
+ size="small"
+ iconRight
+ >
+
+
+ )}
+
);
}
-
-const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => {
- const props = useSpring({
- width: percent,
- y: value,
- from: { width: 0, y: 0 },
- config: animate ? config.default : { duration: 0 },
- });
-
- return (
-
-
{label}
-
-
{props.y?.interpolate(format)}
-
-
-
`${n}%`) }}
- />
-
- {props.width.interpolate(n => `${n.toFixed(0)}%`)}
-
-
-
- );
-};
diff --git a/components/metrics/MetricsTable.module.css b/components/metrics/MetricsTable.module.css
index bbba0009..e93f536e 100644
--- a/components/metrics/MetricsTable.module.css
+++ b/components/metrics/MetricsTable.module.css
@@ -6,95 +6,6 @@
flex-direction: column;
}
-.header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- line-height: 40px;
-}
-
-.title {
- display: flex;
- font-weight: 600;
- font-size: var(--font-size-normal);
-}
-
-.metric {
- font-size: var(--font-size-small);
- font-weight: 600;
- text-align: center;
- width: 100px;
- cursor: pointer;
-}
-
-.row {
- position: relative;
- height: 30px;
- line-height: 30px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 5px;
- overflow: hidden;
-}
-
-.label {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- flex: 2;
-}
-
-.label a {
- color: inherit;
- text-decoration: none;
-}
-
-.label a:hover {
- color: var(--primary400);
-}
-
-.label:empty {
- color: #b3b3b3;
-}
-
-.label:empty:before {
- content: 'Unknown';
-}
-
-.value {
- width: 50px;
- text-align: right;
- margin-right: 10px;
- font-weight: 600;
- cursor: pointer;
-}
-
-.percent {
- position: relative;
- width: 50px;
- color: var(--gray600);
- border-left: 1px solid var(--gray600);
- padding-left: 10px;
- z-index: 1;
-}
-
-.bar {
- position: absolute;
- top: 0;
- left: 0;
- height: 30px;
- opacity: 0.1;
- background: var(--primary400);
- z-index: -1;
-}
-
-.body {
- position: relative;
- flex: 1;
- overflow: hidden;
-}
-
.footer {
display: flex;
justify-content: center;
diff --git a/components/metrics/OSTable.js b/components/metrics/OSTable.js
index 63d4739c..c1790e17 100644
--- a/components/metrics/OSTable.js
+++ b/components/metrics/OSTable.js
@@ -3,15 +3,14 @@ import MetricsTable from './MetricsTable';
import { osFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
-export default function OSTable({ websiteId, token, limit }) {
+export default function OSTable({ websiteId, ...props }) {
return (
}
type="os"
metric={
}
websiteId={websiteId}
- token={token}
- limit={limit}
dataFilter={osFilter}
/>
);
diff --git a/components/metrics/PagesTable.js b/components/metrics/PagesTable.js
index 0acda811..c137589f 100644
--- a/components/metrics/PagesTable.js
+++ b/components/metrics/PagesTable.js
@@ -2,15 +2,16 @@ import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Link from 'next/link';
-import ButtonGroup from 'components/common/ButtonGroup';
-import ButtonLayout from 'components/layout/ButtonLayout';
+import FilterButtons from 'components/common/FilterButtons';
import { urlFilter } from 'lib/filters';
-import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
import usePageQuery from 'hooks/usePageQuery';
import MetricsTable from './MetricsTable';
import styles from './PagesTable.module.css';
-export default function PagesTable({ websiteId, token, websiteDomain, limit, showFilters }) {
+export const FILTER_COMBINED = 0;
+export const FILTER_RAW = 1;
+
+export default function PagesTable({ websiteId, websiteDomain, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const {
resolve,
@@ -48,20 +49,11 @@ export default function PagesTable({ websiteId, token, websiteDomain, limit, sho
type="url"
metric={
}
websiteId={websiteId}
- token={token}
- limit={limit}
dataFilter={urlFilter}
filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }}
renderLabel={renderLink}
+ {...props}
/>
>
);
}
-
-const FilterButtons = ({ buttons, selected, onClick }) => {
- return (
-
-
-
- );
-};
diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js
index 9120a6c3..79fe4917 100644
--- a/components/metrics/PageviewsChart.js
+++ b/components/metrics/PageviewsChart.js
@@ -4,9 +4,18 @@ import tinycolor from 'tinycolor2';
import CheckVisible from 'components/helpers/CheckVisible';
import BarChart from './BarChart';
import useTheme from 'hooks/useTheme';
-import { THEME_COLORS } from 'lib/constants';
+import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants';
-export default function PageviewsChart({ websiteId, data, unit, records, className, loading }) {
+export default function PageviewsChart({
+ websiteId,
+ data,
+ unit,
+ records,
+ className,
+ loading,
+ animationDuration = DEFAULT_ANIMATION_DURATION,
+ ...props
+}) {
const intl = useIntl();
const [theme] = useTheme();
const primaryColor = tinycolor(THEME_COLORS[theme].primary);
@@ -26,7 +35,7 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
data: { datasets },
} = chart;
- datasets[0].data = data.uniques;
+ datasets[0].data = data.sessions;
datasets[0].label = intl.formatMessage({
id: 'metrics.unique-visitors',
defaultMessage: 'Unique visitors',
@@ -48,6 +57,7 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
{visible => (
diff --git a/components/metrics/RealtimeChart.js b/components/metrics/RealtimeChart.js
new file mode 100644
index 00000000..6acfd653
--- /dev/null
+++ b/components/metrics/RealtimeChart.js
@@ -0,0 +1,60 @@
+import React, { useMemo, useRef } from 'react';
+import { format, parseISO, startOfMinute, subMinutes, isBefore } from 'date-fns';
+import PageviewsChart from './PageviewsChart';
+import { getDateArray } from 'lib/date';
+import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants';
+
+function mapData(data) {
+ let last = 0;
+ const arr = [];
+
+ data.reduce((obj, val) => {
+ const { created_at } = val;
+ const t = startOfMinute(parseISO(created_at));
+ if (t.getTime() > last) {
+ obj = { t: format(t, 'yyyy-LL-dd HH:mm:00'), y: 1 };
+ arr.push(obj);
+ last = t;
+ } else {
+ obj.y += 1;
+ }
+ return obj;
+ }, {});
+
+ return arr;
+}
+
+export default function RealtimeChart({ data, unit, ...props }) {
+ const endDate = startOfMinute(new Date());
+ const startDate = subMinutes(endDate, REALTIME_RANGE);
+ const prevEndDate = useRef(endDate);
+
+ const chartData = useMemo(() => {
+ if (data) {
+ return {
+ pageviews: getDateArray(mapData(data.pageviews), startDate, endDate, unit),
+ sessions: getDateArray(mapData(data.sessions), startDate, endDate, unit),
+ };
+ }
+ return { pageviews: [], sessions: [] };
+ }, [data]);
+
+ // Don't animate the bars shifting over because it looks weird
+ const animationDuration = useMemo(() => {
+ if (isBefore(prevEndDate.current, endDate)) {
+ prevEndDate.current = endDate;
+ return 0;
+ }
+ return DEFAULT_ANIMATION_DURATION;
+ }, [data]);
+
+ return (
+
+ );
+}
diff --git a/components/metrics/RealtimeHeader.js b/components/metrics/RealtimeHeader.js
new file mode 100644
index 00000000..c501937a
--- /dev/null
+++ b/components/metrics/RealtimeHeader.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PageHeader from '../layout/PageHeader';
+import DropDown from '../common/DropDown';
+import MetricCard from './MetricCard';
+import styles from './RealtimeHeader.module.css';
+
+export default function RealtimeHeader({ websites, data, websiteId, onSelect }) {
+ const options = [
+ { label: , value: 0 },
+ ].concat(
+ websites.map(({ name, website_id }, index) => ({
+ label: name,
+ value: website_id,
+ divider: index === 0,
+ })),
+ );
+
+ const { pageviews, sessions, events, countries } = data;
+
+ return (
+ <>
+
+
+
+
+
+
+
+ }
+ value={pageviews.length}
+ />
+ }
+ value={sessions.length}
+ />
+ }
+ value={events.length}
+ />
+ }
+ value={countries.length}
+ />
+
+ >
+ );
+}
diff --git a/components/metrics/RealtimeHeader.module.css b/components/metrics/RealtimeHeader.module.css
new file mode 100644
index 00000000..8f948eb1
--- /dev/null
+++ b/components/metrics/RealtimeHeader.module.css
@@ -0,0 +1,3 @@
+.metrics {
+ display: flex;
+}
diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js
new file mode 100644
index 00000000..c2de333b
--- /dev/null
+++ b/components/metrics/RealtimeLog.js
@@ -0,0 +1,168 @@
+import React, { useMemo, useState } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { FixedSizeList } from 'react-window';
+import firstBy from 'thenby';
+import { format } from 'date-fns';
+import Icon from 'components/common/Icon';
+import Tag from 'components/common/Tag';
+import Dot from 'components/common/Dot';
+import FilterButtons from 'components/common/FilterButtons';
+import useLocale from 'hooks/useLocale';
+import useCountryNames from 'hooks/useCountryNames';
+import { BROWSERS } from 'lib/constants';
+import Bolt from 'assets/bolt.svg';
+import Visitor from 'assets/visitor.svg';
+import Eye from 'assets/eye.svg';
+import { stringToColor } from 'lib/format';
+import styles from './RealtimeLog.module.css';
+
+const TYPE_ALL = 0;
+const TYPE_PAGEVIEW = 1;
+const TYPE_SESSION = 2;
+const TYPE_EVENT = 3;
+
+const TYPE_ICONS = {
+ [TYPE_PAGEVIEW]: ,
+ [TYPE_SESSION]: ,
+ [TYPE_EVENT]: ,
+};
+
+export default function RealtimeLog({ data, websites }) {
+ const [locale] = useLocale();
+ const countryNames = useCountryNames(locale);
+ const [filter, setFilter] = useState(TYPE_ALL);
+
+ const logs = useMemo(() => {
+ const { pageviews, sessions, events } = data;
+ const logs = [...pageviews, ...sessions, ...events].sort(firstBy('created_at', -1));
+ if (filter) {
+ return logs.filter(row => getType(row) === filter);
+ }
+ return logs;
+ }, [data, filter]);
+
+ const uuids = useMemo(() => {
+ return data.sessions.reduce((obj, { session_id, session_uuid }) => {
+ obj[session_id] = session_uuid;
+ return obj;
+ }, {});
+ }, [data]);
+
+ const buttons = [
+ {
+ label: ,
+ value: TYPE_ALL,
+ },
+ {
+ label: ,
+ value: TYPE_PAGEVIEW,
+ },
+ {
+ label: ,
+ value: TYPE_SESSION,
+ },
+ {
+ label: ,
+ value: TYPE_EVENT,
+ },
+ ];
+
+ function getType({ view_id, session_id, event_id }) {
+ if (event_id) {
+ return TYPE_EVENT;
+ }
+ if (view_id) {
+ return TYPE_PAGEVIEW;
+ }
+ if (session_id) {
+ return TYPE_SESSION;
+ }
+ return null;
+ }
+
+ function getIcon(row) {
+ return TYPE_ICONS[getType(row)];
+ }
+
+ function getWebsite({ website_id }) {
+ return websites.find(n => n.website_id === website_id)?.name;
+ }
+
+ function getDetail({
+ event_type,
+ event_value,
+ view_id,
+ session_id,
+ url,
+ browser,
+ os,
+ country,
+ device,
+ }) {
+ if (event_type) {
+ return (
+
+ {event_type} {event_value}
+
+ );
+ }
+ if (view_id) {
+ return url;
+ }
+ if (session_id) {
+ return (
+ {countryNames[country]},
+ browser: BROWSERS[browser],
+ os,
+ device,
+ }}
+ />
+ );
+ }
+ }
+
+ function getTime({ created_at }) {
+ return format(new Date(created_at), 'h:mm:ss');
+ }
+
+ function getColor(row) {
+ const { session_id } = row;
+
+ return stringToColor(uuids[session_id] || `${session_id}${getWebsite(row)}`);
+ }
+
+ const Row = ({ index, style }) => {
+ const row = logs[index];
+ return (
+
+
+
+
+
{getTime(row)}
+
+
+ {getDetail(row)}
+
+
{getWebsite(row)}
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+ {Row}
+
+
+
+ );
+}
diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css
new file mode 100644
index 00000000..99335a52
--- /dev/null
+++ b/components/metrics/RealtimeLog.module.css
@@ -0,0 +1,46 @@
+.table {
+ font-size: var(--font-size-xsmall);
+ overflow: hidden;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 16px;
+ line-height: 40px;
+ font-weight: 600;
+}
+
+.row {
+ display: flex;
+ align-items: center;
+ height: 40px;
+ border-bottom: 1px solid var(--gray300);
+}
+
+.body {
+ overflow: auto;
+}
+
+.icon {
+ margin-right: 10px;
+}
+
+.time {
+ min-width: 60px;
+ overflow: hidden;
+}
+
+.website {
+ text-align: right;
+ padding: 0 20px;
+}
+
+.detail {
+ display: flex;
+ flex: 1;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
diff --git a/components/metrics/RealtimeViews.js b/components/metrics/RealtimeViews.js
new file mode 100644
index 00000000..f07facef
--- /dev/null
+++ b/components/metrics/RealtimeViews.js
@@ -0,0 +1,100 @@
+import React, { useMemo, useState, useCallback } from 'react';
+import { FormattedMessage } from 'react-intl';
+import firstBy from 'thenby';
+import { percentFilter } from 'lib/filters';
+import DataTable from './DataTable';
+import FilterButtons from 'components/common/FilterButtons';
+
+const FILTER_REFERRERS = 0;
+const FILTER_PAGES = 1;
+
+export default function RealtimeViews({ websiteId, data, websites }) {
+ const { pageviews } = data;
+ const [filter, setFilter] = useState(FILTER_REFERRERS);
+ const domains = useMemo(() => websites.map(({ domain }) => domain), [websites]);
+ const getDomain = useCallback(
+ id => websites.find(({ website_id }) => website_id === id)?.domain,
+ [websites],
+ );
+
+ const buttons = [
+ {
+ label: ,
+ value: FILTER_REFERRERS,
+ },
+ {
+ label: ,
+ value: FILTER_PAGES,
+ },
+ ];
+
+ const [referrers, pages] = useMemo(() => {
+ if (pageviews) {
+ const referrers = percentFilter(
+ pageviews
+ .reduce((arr, { referrer }) => {
+ if (referrer?.startsWith('http')) {
+ const hostname = new URL(referrer).hostname.replace(/^www\./, '');
+
+ if (hostname && !domains.includes(hostname)) {
+ const row = arr.find(({ x }) => x === hostname);
+
+ if (!row) {
+ arr.push({ x: hostname, y: 1 });
+ } else {
+ row.y += 1;
+ }
+ }
+ }
+ return arr;
+ }, [])
+ .sort(firstBy('y', -1)),
+ );
+
+ const pages = percentFilter(
+ pageviews
+ .reduce((arr, { url, website_id }) => {
+ if (url?.startsWith('/')) {
+ if (!websiteId) {
+ url = `${getDomain(website_id)}${url}`;
+ }
+ const row = arr.find(({ x }) => x === url);
+
+ if (!row) {
+ arr.push({ x: url, y: 1 });
+ } else {
+ row.y += 1;
+ }
+ }
+ return arr;
+ }, [])
+ .sort(firstBy('y', -1)),
+ );
+
+ return [referrers, pages];
+ }
+ return [];
+ }, [pageviews]);
+
+ return (
+ <>
+
+ {filter === FILTER_REFERRERS && (
+ }
+ metric={}
+ data={referrers}
+ height={400}
+ />
+ )}
+ {filter === FILTER_PAGES && (
+ }
+ metric={}
+ data={pages}
+ height={400}
+ />
+ )}
+ >
+ );
+}
diff --git a/components/metrics/ReferrersTable.js b/components/metrics/ReferrersTable.js
index df7a8c15..2d51ab74 100644
--- a/components/metrics/ReferrersTable.js
+++ b/components/metrics/ReferrersTable.js
@@ -1,12 +1,14 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable';
+import FilterButtons from 'components/common/FilterButtons';
import { refFilter } from 'lib/filters';
-import ButtonGroup from 'components/common/ButtonGroup';
-import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
-import ButtonLayout from '../layout/ButtonLayout';
-export default function ReferrersTable({ websiteId, websiteDomain, token, limit, showFilters }) {
+export const FILTER_DOMAIN_ONLY = 0;
+export const FILTER_COMBINED = 1;
+export const FILTER_RAW = 2;
+
+export default function ReferrersTable({ websiteId, websiteDomain, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const buttons = [
@@ -35,13 +37,12 @@ export default function ReferrersTable({ websiteId, websiteDomain, token, limit,
<>
{showFilters && }
}
type="referrer"
metric={}
websiteId={websiteId}
websiteDomain={websiteDomain}
- token={token}
- limit={limit}
dataFilter={refFilter}
filterOptions={{
domain: websiteDomain,
@@ -53,11 +54,3 @@ export default function ReferrersTable({ websiteId, websiteDomain, token, limit,
>
);
}
-
-const FilterButtons = ({ buttons, selected, onClick }) => {
- return (
-
-
-
- );
-};
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index ea86ad3e..07ba5161 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -13,15 +13,18 @@ import usePageQuery from 'hooks/usePageQuery';
import { getDateArray, getDateLength } from 'lib/date';
import Times from 'assets/times.svg';
import styles from './WebsiteChart.module.css';
+import ErrorMessage from '../common/ErrorMessage';
+import useShareToken from '../../hooks/useShareToken';
+import { TOKEN_HEADER } from '../../lib/constants';
export default function WebsiteChart({
websiteId,
- token,
title,
stickyHeader = false,
showLink = false,
onDataLoad = () => {},
}) {
+ const shareToken = useShareToken();
const [dateRange, setDateRange] = useDateRange(websiteId);
const { startDate, endDate, unit, value, modified } = dateRange;
const [timezone] = useTimezone();
@@ -31,27 +34,30 @@ export default function WebsiteChart({
query: { url },
} = usePageQuery();
- const { data, loading } = useFetch(
+ const { data, loading, error } = useFetch(
`/api/website/${websiteId}/pageviews`,
{
- start_at: +startDate,
- end_at: +endDate,
- unit,
- tz: timezone,
- url,
- token,
+ params: {
+ start_at: +startDate,
+ end_at: +endDate,
+ unit,
+ tz: timezone,
+ url,
+ },
+ onDataLoad,
+ headers: { [TOKEN_HEADER]: shareToken?.token },
},
- { onDataLoad, update: [modified] },
+ [modified],
);
- const [pageviews, uniques] = useMemo(() => {
+ const chartData = useMemo(() => {
if (data) {
- return [
- getDateArray(data.pageviews, startDate, endDate, unit),
- getDateArray(data.uniques, startDate, endDate, unit),
- ];
+ return {
+ pageviews: getDateArray(data.pageviews, startDate, endDate, unit),
+ sessions: getDateArray(data.sessions, startDate, endDate, unit),
+ };
}
- return [[], []];
+ return { pageviews: [], sessions: [] };
}, [data]);
function handleCloseFilter() {
@@ -60,7 +66,7 @@ export default function WebsiteChart({
return (
-
+
{url && }
-
+
+ {error &&
}
{title}
-
+
{showLink && (
@@ -24,7 +24,7 @@ export default function WebsiteHeader({ websiteId, token, title, showLink = fals
size="small"
iconRight
>
-
+
)}
diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js
new file mode 100644
index 00000000..1a2a3f54
--- /dev/null
+++ b/components/pages/RealtimeDashboard.js
@@ -0,0 +1,158 @@
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { subMinutes, startOfMinute } from 'date-fns';
+import firstBy from 'thenby';
+import Page from 'components/layout/Page';
+import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout';
+import RealtimeChart from 'components/metrics/RealtimeChart';
+import RealtimeLog from 'components/metrics/RealtimeLog';
+import RealtimeHeader from 'components/metrics/RealtimeHeader';
+import WorldMap from 'components/common/WorldMap';
+import DataTable from 'components/metrics/DataTable';
+import RealtimeViews from 'components/metrics/RealtimeViews';
+import useFetch from 'hooks/useFetch';
+import useLocale from 'hooks/useLocale';
+import useCountryNames from 'hooks/useCountryNames';
+import { percentFilter } from 'lib/filters';
+import { TOKEN_HEADER, REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
+import styles from './RealtimeDashboard.module.css';
+
+function mergeData(state, data, time) {
+ const ids = state.map(({ __id }) => __id);
+ return state
+ .concat(data.filter(({ __id }) => !ids.includes(__id)))
+ .filter(({ created_at }) => new Date(created_at).getTime() >= time);
+}
+
+function filterWebsite(data, id) {
+ return data.filter(({ website_id }) => website_id === id);
+}
+
+export default function RealtimeDashboard() {
+ const [locale] = useLocale();
+ const countryNames = useCountryNames(locale);
+ const [data, setData] = useState();
+ const [websiteId, setWebsiteId] = useState(0);
+ const { data: init, loading } = useFetch('/api/realtime/init');
+ const { data: updates } = useFetch('/api/realtime/update', {
+ params: { start_at: data?.timestamp },
+ disabled: !init?.websites?.length || !data,
+ interval: REALTIME_INTERVAL,
+ headers: { [TOKEN_HEADER]: init?.token },
+ });
+
+ const renderCountryName = useCallback(
+ ({ x }) => {countryNames[x]},
+ [countryNames],
+ );
+
+ const realtimeData = useMemo(() => {
+ if (data) {
+ const { pageviews, sessions, events } = data;
+
+ if (websiteId) {
+ return {
+ pageviews: filterWebsite(pageviews, websiteId),
+ sessions: filterWebsite(sessions, websiteId),
+ events: filterWebsite(events, websiteId),
+ };
+ }
+ }
+
+ return data;
+ }, [data, websiteId]);
+
+ const countries = useMemo(() => {
+ if (realtimeData?.sessions) {
+ return percentFilter(
+ realtimeData.sessions
+ .reduce((arr, { country }) => {
+ if (country) {
+ const row = arr.find(({ x }) => x === country);
+
+ if (!row) {
+ arr.push({ x: country, y: 1 });
+ } else {
+ row.y += 1;
+ }
+ }
+ return arr;
+ }, [])
+ .sort(firstBy('y', -1)),
+ );
+ }
+ return [];
+ }, [realtimeData?.sessions]);
+
+ useEffect(() => {
+ if (init && !data) {
+ const { websites, data } = init;
+
+ setData({ websites, ...data });
+ }
+ }, [init]);
+
+ useEffect(() => {
+ if (updates) {
+ const { pageviews, sessions, events, timestamp } = updates;
+ const time = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime();
+
+ setData(state => ({
+ ...state,
+ pageviews: mergeData(state.pageviews, pageviews, time),
+ sessions: mergeData(state.sessions, sessions, time),
+ events: mergeData(state.events, events, time),
+ timestamp,
+ }));
+ }
+ }, [updates]);
+
+ if (!init || !data || loading) {
+ return null;
+ }
+
+ const { websites } = data;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ metric={}
+ data={countries}
+ renderLabel={renderCountryName}
+ height={500}
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/pages/RealtimeDashboard.module.css b/components/pages/RealtimeDashboard.module.css
new file mode 100644
index 00000000..b9b5a632
--- /dev/null
+++ b/components/pages/RealtimeDashboard.module.css
@@ -0,0 +1,7 @@
+.container {
+ display: flex;
+}
+
+.chart {
+ margin-bottom: 30px;
+}
diff --git a/components/pages/TestConsole.js b/components/pages/TestConsole.js
index f6fa8a23..fef6c620 100644
--- a/components/pages/TestConsole.js
+++ b/components/pages/TestConsole.js
@@ -7,7 +7,7 @@ import Page from '../layout/Page';
import PageHeader from '../layout/PageHeader';
import useFetch from '../../hooks/useFetch';
import DropDown from '../common/DropDown';
-import styles from './Test.module.css';
+import styles from './TestConsole.module.css';
import WebsiteChart from '../metrics/WebsiteChart';
import EventsChart from '../metrics/EventsChart';
import Button from '../common/Button';
diff --git a/components/pages/Test.module.css b/components/pages/TestConsole.module.css
similarity index 100%
rename from components/pages/Test.module.css
rename to components/pages/TestConsole.module.css
diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js
index 66545928..6c310909 100644
--- a/components/pages/WebsiteDetails.js
+++ b/components/pages/WebsiteDetails.js
@@ -4,6 +4,7 @@ import classNames from 'classnames';
import WebsiteChart from 'components/metrics/WebsiteChart';
import WorldMap from 'components/common/WorldMap';
import Page from 'components/layout/Page';
+import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout';
import MenuLayout from 'components/layout/MenuLayout';
import Link from 'components/common/Link';
import Loading from 'components/common/Loading';
@@ -19,6 +20,8 @@ import EventsTable from '../metrics/EventsTable';
import EventsChart from '../metrics/EventsChart';
import useFetch from 'hooks/useFetch';
import usePageQuery from 'hooks/usePageQuery';
+import useShareToken from 'hooks/useShareToken';
+import { DEFAULT_ANIMATION_DURATION, TOKEN_HEADER } from 'lib/constants';
const views = {
url: PagesTable,
@@ -30,8 +33,11 @@ const views = {
event: EventsTable,
};
-export default function WebsiteDetails({ websiteId, token }) {
- const { data } = useFetch(`/api/website/${websiteId}`, { token });
+export default function WebsiteDetails({ websiteId }) {
+ const shareToken = useShareToken();
+ const { data } = useFetch(`/api/website/${websiteId}`, {
+ headers: { [TOKEN_HEADER]: shareToken?.token },
+ });
const [chartLoaded, setChartLoaded] = useState(false);
const [countryData, setCountryData] = useState();
const [eventsData, setEventsData] = useState();
@@ -50,7 +56,7 @@ export default function WebsiteDetails({ websiteId, token }) {
icon={}
size="small"
>
-
+
);
@@ -91,7 +97,6 @@ export default function WebsiteDetails({ websiteId, token }) {
const tableProps = {
websiteId,
- token,
websiteDomain: data?.domain,
limit: 10,
};
@@ -100,7 +105,7 @@ export default function WebsiteDetails({ websiteId, token }) {
function handleDataLoad() {
if (!chartLoaded) {
- setTimeout(() => setChartLoaded(true), 300);
+ setTimeout(() => setChartLoaded(true), DEFAULT_ANIMATION_DURATION);
}
}
@@ -114,7 +119,6 @@ export default function WebsiteDetails({ websiteId, token }) {
{!chartLoaded && }
{chartLoaded && !view && (
- <>
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
- 0 })}
- >
-
+
+
+ 0 })}>
+
-
-
-
-
-
- >
+
+
+
+
+
+
)}
- {view && (
+ {view && chartLoaded && (
-
+
)}
diff --git a/components/pages/WebsiteDetails.module.css b/components/pages/WebsiteDetails.module.css
index 750a1ac7..cd771c96 100644
--- a/components/pages/WebsiteDetails.module.css
+++ b/components/pages/WebsiteDetails.module.css
@@ -26,37 +26,10 @@
transform: rotate(180deg);
}
-.row {
- border-top: 1px solid var(--gray300);
- min-height: 430px;
-}
-
-.row > [class*='col-'] {
- border-left: 1px solid var(--gray300);
- padding: 20px;
-}
-
-.row > [class*='col-']:first-child {
- border-left: 0;
- padding-left: 0;
-}
-
-.row > [class*='col-']:last-child {
- padding-right: 0;
-}
-
.hidden {
display: none;
}
-@media only screen and (max-width: 992px) {
- .row {
- border: 0;
- }
-
- .row > [class*='col-'] {
- border-top: 1px solid var(--gray300);
- border-left: 0;
- padding: 0;
- }
+.eventschart {
+ padding: 30px 0;
}
diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js
index 0df24877..cd96ae9f 100644
--- a/components/pages/WebsiteList.js
+++ b/components/pages/WebsiteList.js
@@ -1,17 +1,15 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
-import { useRouter } from 'next/router';
+import Link from 'components/common/Link';
import WebsiteChart from 'components/metrics/WebsiteChart';
import Page from 'components/layout/Page';
-import Button from 'components/common/Button';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import useFetch from 'hooks/useFetch';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteList.module.css';
export default function WebsiteList({ userId }) {
- const router = useRouter();
- const { data } = useFetch('/api/websites', { user_id: userId });
+ const { data } = useFetch('/api/websites', { params: { user_id: userId } });
if (!data) {
return null;
@@ -33,9 +31,9 @@ export default function WebsiteList({ userId }) {
/>
}
>
- } size="medium" onClick={() => router.push('/settings')}>
+ } iconRight>
-
+
)}
diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js
index aa206fa9..b2b0011e 100644
--- a/components/settings/AccountSettings.js
+++ b/components/settings/AccountSettings.js
@@ -25,7 +25,7 @@ export default function AccountSettings() {
const [deleteAccount, setDeleteAccount] = useState();
const [saved, setSaved] = useState(0);
const [message, setMessage] = useState();
- const { data } = useFetch(`/api/accounts`, {}, { update: [saved] });
+ const { data } = useFetch(`/api/accounts`, {}, [saved]);
const Checkmark = ({ is_admin }) => (is_admin ? } size="medium" /> : null);
@@ -42,10 +42,10 @@ export default function AccountSettings() {
row.username !== 'admin' ? (
} size="small" onClick={() => setEditAccount(row)}>
-
+
} size="small" onClick={() => setDeleteAccount(row)}>
-
+
) : null;
@@ -98,12 +98,12 @@ export default function AccountSettings() {
} size="small" onClick={() => setAddAccount(true)}>
-
+
{editAccount && (
-
}>
+
}>
)}
{addAccount && (
- }>
+ }>
)}
{deleteAccount && (
}
+ title={}
>
>
);
diff --git a/components/settings/LanguageButton.js b/components/settings/LanguageButton.js
index e643f070..2d340ed0 100644
--- a/components/settings/LanguageButton.js
+++ b/components/settings/LanguageButton.js
@@ -4,6 +4,7 @@ import { menuOptions } from 'lib/lang';
import useLocale from 'hooks/useLocale';
import MenuButton from 'components/common/MenuButton';
import Globe from 'assets/globe.svg';
+import styles from './LanguageButton.module.css';
export default function LanguageButton() {
const [locale, setLocale] = useLocale();
@@ -32,6 +33,7 @@ export default function LanguageButton() {
icon={}
options={menuOptions}
value={locale}
+ menuClassName={styles.menu}
renderValue={option => option?.display}
onSelect={handleSelect}
/>
diff --git a/components/settings/LanguageButton.module.css b/components/settings/LanguageButton.module.css
new file mode 100644
index 00000000..e1f840d5
--- /dev/null
+++ b/components/settings/LanguageButton.module.css
@@ -0,0 +1,25 @@
+.menu {
+ display: flex;
+ flex-flow: row wrap;
+ min-width: 500px;
+ max-width: 100vw;
+ padding: 10px;
+}
+
+.menu div {
+ border-radius: 5px;
+ min-width: calc(100% / 3);
+}
+
+@media only screen and (max-width: 992px) {
+ .menu {
+ min-width: 90vw;
+ transform: translateX(calc(40vw));
+ }
+}
+
+@media only screen and (max-width: 768px) {
+ .menu div {
+ min-width: 50%;
+ }
+}
diff --git a/components/settings/ProfileSettings.js b/components/settings/ProfileSettings.js
index 6fe18d19..ecd9252d 100644
--- a/components/settings/ProfileSettings.js
+++ b/components/settings/ProfileSettings.js
@@ -34,7 +34,7 @@ export default function ProfileSettings() {
} size="small" onClick={() => setChangePassword(true)}>
-
+
@@ -57,7 +57,7 @@ export default function ProfileSettings() {
{changePassword && (
}
+ title={}
>
>
);
diff --git a/components/settings/WebsiteSettings.js b/components/settings/WebsiteSettings.js
index 17fc5952..0ff6246d 100644
--- a/components/settings/WebsiteSettings.js
+++ b/components/settings/WebsiteSettings.js
@@ -29,7 +29,7 @@ export default function WebsiteSettings() {
const [showUrl, setShowUrl] = useState();
const [saved, setSaved] = useState(0);
const [message, setMessage] = useState();
- const { data } = useFetch(`/api/websites`, {}, { update: [saved] });
+ const { data } = useFetch(`/api/websites`, {}, [saved]);
const Buttons = row => (
@@ -52,10 +52,10 @@ export default function WebsiteSettings() {
onClick={() => setShowCode(row)}
/>
} size="small" onClick={() => setEditWebsite(row)}>
-
+
} size="small" onClick={() => setDeleteWebsite(row)}>
-
+
);
@@ -113,7 +113,7 @@ export default function WebsiteSettings() {
}
>
} size="medium" onClick={() => setAddWebsite(true)}>
-
+
);
@@ -125,23 +125,23 @@ export default function WebsiteSettings() {
} size="small" onClick={() => setAddWebsite(true)}>
-
+
{editWebsite && (
- }>
+ }>
)}
{addWebsite && (
- }>
+ }>
)}
{deleteWebsite && (
}
+ title={}
>
)}
{showCode && (
- }>
+ }>
)}
{showUrl && (
- }>
+ }>
)}
diff --git a/hooks/useFetch.js b/hooks/useFetch.js
index 907f45af..7f97d0fb 100644
--- a/hooks/useFetch.js
+++ b/hooks/useFetch.js
@@ -4,30 +4,34 @@ import { get } from 'lib/web';
import { updateQuery } from 'redux/actions/queries';
import { useRouter } from 'next/router';
-export default function useFetch(url, params = {}, options = {}) {
+export default function useFetch(url, options = {}, update = []) {
const dispatch = useDispatch();
const [data, setData] = useState();
const [status, setStatus] = useState();
const [error, setError] = useState();
const [loading, setLoadiing] = useState(false);
+ const [count, setCount] = useState(0);
const { basePath } = useRouter();
- const keys = Object.keys(params)
- .sort()
- .map(key => params[key]);
- const { update = [], onDataLoad = () => {} } = options;
+ const { params = {}, disabled, headers, delay = 0, interval, onDataLoad } = options;
- async function loadData() {
+ async function loadData(params) {
try {
setLoadiing(true);
setError(null);
const time = performance.now();
- const { data, status } = await get(`${basePath}${url}`, params);
+ const { data, status } = await get(`${basePath}${url}`, params, headers);
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
- setData(data);
+ if (status >= 400) {
+ setError(data);
+ setData(null);
+ } else {
+ setData(data);
+ }
+
setStatus(status);
- onDataLoad(data);
+ onDataLoad?.(data);
} catch (e) {
console.error(e);
setError(e);
@@ -37,18 +41,24 @@ export default function useFetch(url, params = {}, options = {}) {
}
useEffect(() => {
- if (url) {
- const { interval, delay = 0 } = options;
+ if (url && !disabled) {
+ const id = setTimeout(() => loadData(params), delay);
- setTimeout(() => loadData(), delay);
+ return () => {
+ clearTimeout(id);
+ };
+ }
+ }, [url, !!disabled, count, ...update]);
- const id = interval ? setInterval(() => loadData(), interval) : null;
+ useEffect(() => {
+ if (interval && !disabled) {
+ const id = setInterval(() => setCount(state => state + 1), interval);
return () => {
clearInterval(id);
};
}
- }, [url, ...keys, ...update]);
+ }, [interval, !!disabled]);
return { data, status, error, loading };
}
diff --git a/hooks/useForceSSL.js b/hooks/useForceSSL.js
new file mode 100644
index 00000000..b9d95e19
--- /dev/null
+++ b/hooks/useForceSSL.js
@@ -0,0 +1,14 @@
+import { useEffect } from 'react';
+import { useRouter } from 'next/router';
+
+export default function useForceSSL(enabled) {
+ const router = useRouter();
+
+ useEffect(() => {
+ if (enabled && typeof window !== 'undefined' && /^http:\/\//.test(location.href)) {
+ router.push(location.href.replace(/^http:\/\//, 'https://'));
+ }
+ }, [enabled]);
+
+ return null;
+}
diff --git a/hooks/useShareToken.js b/hooks/useShareToken.js
new file mode 100644
index 00000000..16ee11d0
--- /dev/null
+++ b/hooks/useShareToken.js
@@ -0,0 +1,25 @@
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { get } from 'lib/web';
+import { setShareToken } from 'redux/actions/app';
+
+export default function useShareToken(shareId) {
+ const dispatch = useDispatch();
+ const shareToken = useSelector(state => state.app.shareToken);
+
+ async function loadToken(id) {
+ const { data } = await get(`/api/share/${id}`);
+
+ if (data) {
+ dispatch(setShareToken(data));
+ }
+ }
+
+ useEffect(() => {
+ if (shareId) {
+ loadToken(shareId);
+ }
+ }, [shareId]);
+
+ return shareToken;
+}
diff --git a/hooks/useVersion.js b/hooks/useVersion.js
index d8e3d699..79b97a34 100644
--- a/hooks/useVersion.js
+++ b/hooks/useVersion.js
@@ -1,27 +1,23 @@
import { useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import semver from 'semver';
-import { getItem, setItem } from 'lib/web';
import { checkVersion } from 'redux/actions/app';
import { VERSION_CHECK } from 'lib/constants';
+import { getItem, setItem } from 'lib/web';
-export default function useVersion() {
+export default function useVersion(check) {
const dispatch = useDispatch();
const versions = useSelector(state => state.app.versions);
- const lastCheck = getItem(VERSION_CHECK);
-
- const { current, latest } = versions;
- const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
+ const checked = versions.latest === getItem(VERSION_CHECK)?.version;
const updateCheck = useCallback(() => {
- setItem(VERSION_CHECK, { version: latest, time: Date.now() });
+ setItem(VERSION_CHECK, { version: versions.latest, time: Date.now() });
}, [versions]);
useEffect(() => {
- if (!versions.latest) {
+ if (check && !versions.latest) {
dispatch(checkVersion());
}
- }, [versions]);
+ }, [versions, check]);
- return { ...versions, hasUpdate, updateCheck };
+ return { ...versions, checked, updateCheck };
}
diff --git a/lang/da-DK.json b/lang/da-DK.json
index cd96cb3f..b1b5c512 100644
--- a/lang/da-DK.json
+++ b/lang/da-DK.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Tilføj konto",
- "button.add-website": "Tilføj hjemmeside",
- "button.back": "Tilbage",
- "button.cancel": "Afvis",
- "button.change-password": "Skift adgangskode",
- "button.copy-to-clipboard": "Kopier til udklipsholder",
- "button.date-range": "Datointerval",
- "button.delete": "Slet",
- "button.dismiss": "Dismiss",
- "button.edit": "Rediger",
- "button.login": "Log ind",
- "button.more": "Mere",
- "button.refresh": "Opdater",
- "button.reset": "Reset",
- "button.save": "Gem",
- "button.single-day": "Enkelt dag",
- "button.view-details": "Vis detajler",
"label.accounts": "Kontoer",
+ "label.add-account": "Tilføj konto",
+ "label.add-website": "Tilføj hjemmeside",
"label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Tilbage",
+ "label.cancel": "Afvis",
+ "label.change-password": "Skift adgangskode",
"label.confirm-password": "Godkendt adgangskode",
+ "label.copy-to-clipboard": "Kopier til udklipsholder",
"label.current-password": "Nuværende adgangskode",
"label.custom-range": "Tilpasset interval",
"label.dashboard": "Betjeningspanel",
+ "label.date-range": "Datointerval",
"label.default-date-range": "Default date range",
+ "label.delete": "Slet",
+ "label.delete-account": "Slet konto",
+ "label.delete-website": "Slet hjemmeside",
+ "label.dismiss": "Dismiss",
"label.domain": "Domæne",
+ "label.edit": "Rediger",
+ "label.edit-account": "Rediger konto",
+ "label.edit-website": "Rediger hjemmeside",
"label.enable-share-url": "Aktivér delings-URL",
"label.invalid": "Ugyldig",
"label.invalid-domain": "Ugyldigt domæne",
"label.last-days": "Sidste {x} dage",
"label.last-hours": "Sidste {x} timer",
"label.logged-in-as": "Loggede ind som {username}",
+ "label.login": "Log ind",
"label.logout": "Log ud",
+ "label.more": "Mere",
"label.name": "Navn",
"label.new-password": "Ny adgangskode",
"label.password": "Adgangskode",
"label.passwords-dont-match": "Adgangskoder matcher ikke",
"label.profile": "Profil",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Opdater",
"label.required": "Påkrævet",
+ "label.reset": "Reset",
+ "label.save": "Gem",
"label.settings": "Indstillinger",
+ "label.share-url": "Del URL",
+ "label.single-day": "Enkelt dag",
"label.this-month": "Denne måned",
"label.this-week": "Denne uge",
"label.this-year": "Dette år",
"label.timezone": "Timezone",
"label.today": "Idag",
+ "label.tracking-code": "Sporingskode",
"label.unknown": "Ukendt",
"label.username": "Brugernavn",
+ "label.view-details": "Vis detajler",
"label.websites": "Hjemmesider",
"message.active-users": "{x} nuværende {x, plural, one {bruger} other {brugere}}",
"message.confirm-delete": "Er du sikker på at du vil slette {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Få sporingskode",
"message.go-to-settings": "Gå til betjeningspanel",
"message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tilgængelig.",
"message.no-websites-configured": "Du har ikke konfigureret nogen websteder.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Henvisninger",
"metrics.unique-visitors": "Unikke besøgende",
"metrics.views": "Visninger",
- "metrics.visitors": "Besøgende",
- "title.add-account": "Tilføj konto",
- "title.add-website": "Tilføj hjemmeside",
- "title.change-password": "Skift adgangskode",
- "title.delete-account": "Slet konto",
- "title.delete-website": "Slet hjemmeside",
- "title.edit-account": "Rediger konto",
- "title.edit-website": "Rediger hjemmeside",
- "title.share-url": "Del URL",
- "title.tracking-code": "Sporingskode"
+ "metrics.visitors": "Besøgende"
}
diff --git a/lang/de-DE.json b/lang/de-DE.json
index 7c45d131..ac9ac040 100644
--- a/lang/de-DE.json
+++ b/lang/de-DE.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Konto hinzufügen",
- "button.add-website": "Webseite hinzufügen",
- "button.back": "Zurück",
- "button.cancel": "Abbrechen",
- "button.change-password": "Passwort ändern",
- "button.copy-to-clipboard": "In die Zwischenablage kopieren",
- "button.date-range": "Datumsbereich",
- "button.delete": "Löschen",
- "button.dismiss": "Dismiss",
- "button.edit": "Bearbeiten",
- "button.login": "Anmelden",
- "button.more": "Mehr",
- "button.refresh": "Aktualisieren",
- "button.reset": "Zurücksetzen",
- "button.save": "Speichern",
- "button.single-day": "Ein Tag",
- "button.view-details": "Details anzeigen",
"label.accounts": "Konten",
+ "label.add-account": "Konto hinzugfügen",
+ "label.add-website": "Webseite hinzufügen",
"label.administrator": "Administrator",
+ "label.all": "Alle",
+ "label.all-websites": "Alle Webseiten",
+ "label.back": "Zurück",
+ "label.cancel": "Abbrechen",
+ "label.change-password": "Passwort ändern",
"label.confirm-password": "Passwort wiederholen",
+ "label.copy-to-clipboard": "In die Zwischenablage kopieren",
"label.current-password": "Derzeitiges Passwort",
"label.custom-range": "Benutzerdefinierter Bereich",
"label.dashboard": "Übersicht",
+ "label.date-range": "Datumsbereich",
"label.default-date-range": "Voreingestellter Datumsbereich",
+ "label.delete": "Löschen",
+ "label.delete-account": "Konto löschen",
+ "label.delete-website": "Webseite löschen",
+ "label.dismiss": "Verwerfen",
"label.domain": "Domain",
+ "label.edit": "Bearbeiten",
+ "label.edit-account": "Konto bearbeiten",
+ "label.edit-website": "Webseite bearbeiten",
"label.enable-share-url": "Freigabe-URL aktivieren",
"label.invalid": "Ungültig",
"label.invalid-domain": "Ungültige Domain",
"label.last-days": "Letzten {x} Tage",
"label.last-hours": "Letzten {x} Stunden",
"label.logged-in-as": "Angemeldet als {username}",
+ "label.login": "Anmelden",
"label.logout": "Abmelden",
+ "label.more": "Mehr",
"label.name": "Name",
"label.new-password": "Neues Passwort",
"label.password": "Passwort",
"label.passwords-dont-match": "Passwörter stimmen nicht überein",
"label.profile": "Profil",
+ "label.realtime": "Echtzeit",
+ "label.realtime-logs": "Echtzeit Logs",
+ "label.refresh": "Aktualisieren",
"label.required": "Erforderlich",
+ "label.reset": "Zurücksetzen",
+ "label.save": "Speichern",
"label.settings": "Einstellungen",
+ "label.share-url": "Freigabe-URL",
+ "label.single-day": "Ein Tag",
"label.this-month": "Diesen Monat",
"label.this-week": "Diese Woche",
"label.this-year": "Dieses Jahr",
"label.timezone": "Zeitzone",
"label.today": "Heute",
+ "label.tracking-code": "Tracking Kennung",
"label.unknown": "Unbekannt",
"label.username": "Benutzername",
+ "label.view-details": "Details anzeigen",
"label.websites": "Webseiten",
"message.active-users": "{x} {x, plural, one {aktiver Besucher} other {aktive Besucher}}",
"message.confirm-delete": "Sind sie sich sicher {target} zu löschen?",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "Erstelle Tracking Kennung",
"message.go-to-settings": "Zu den Einstellungen",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "Besucher aus {country} benutzt {browser} auf {os} {device}",
+ "message.new-version-available": "Eine neue Version umami {version} ist verfügbar!",
"message.no-data-available": "Keine Daten vorhanden.",
"message.no-websites-configured": "Es ist keine Webseite vorhanden.",
"message.page-not-found": "Seite nicht gefunden.",
@@ -67,7 +78,7 @@
"metrics.actions": "Aktionen",
"metrics.average-visit-time": "Durchschn. Besuchszeit",
"metrics.bounce-rate": "Absprungrate",
- "metrics.browsers": "Browsers",
+ "metrics.browsers": "Browser",
"metrics.countries": "Länder",
"metrics.device.desktop": "Desktop",
"metrics.device.laptop": "Laptop",
@@ -84,14 +95,5 @@
"metrics.referrers": "Referrers",
"metrics.unique-visitors": "Eindeutige Besucher",
"metrics.views": "Aufrufe",
- "metrics.visitors": "Besucher",
- "title.add-account": "Konto hinzugfügen",
- "title.add-website": "Webseite hinzufügen",
- "title.change-password": "Passwort ändern",
- "title.delete-account": "Konto löschen",
- "title.delete-website": "Webseite löschen",
- "title.edit-account": "Konto bearbeiten",
- "title.edit-website": "Webseite bearbeiten",
- "title.share-url": "Freigabe-URL",
- "title.tracking-code": "Tracking Kennung"
+ "metrics.visitors": "Besucher"
}
diff --git a/lang/el-GR.json b/lang/el-GR.json
index 14cd69ca..3de40ea0 100644
--- a/lang/el-GR.json
+++ b/lang/el-GR.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Προσθήκη λογαριασμού",
- "button.add-website": "Προσθήκη ιστότοπου",
- "button.back": "Πίσω",
- "button.cancel": "Ακύρωση",
- "button.change-password": "Αλλαγή κωδικού",
- "button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο",
- "button.date-range": "Εύρος ημερομηνιών",
- "button.delete": "Διαγραφή",
- "button.dismiss": "Dismiss",
- "button.edit": "Επεξεργασία",
- "button.login": "Είσοδος",
- "button.more": "Περισσότερα",
- "button.refresh": "Ανανέωση",
- "button.reset": "Επαναφορά",
- "button.save": "Αποθήκευση",
- "button.single-day": "Ημερήσια",
- "button.view-details": "Λεπτομέρειες",
"label.accounts": "Λογαριασμοί",
+ "label.add-account": "Προσθήκη λογαριασμού",
+ "label.add-website": "Προσθήκη ιστότοπου",
"label.administrator": "Διαχειριστής",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Πίσω",
+ "label.cancel": "Ακύρωση",
+ "label.change-password": "Αλλαγή κωδικού",
"label.confirm-password": "Επιβεβαίωση κωδικού",
+ "label.copy-to-clipboard": "Αντιγραφή στο πρόχειρο",
"label.current-password": "Τωρινός κωδικός πρόσβασης",
"label.custom-range": "Προσαρμοσμένο εύρος",
"label.dashboard": "Πίνακας",
+ "label.date-range": "Εύρος ημερομηνιών",
"label.default-date-range": "Προεπιλεγμένο εύρος ημερομηνιών",
+ "label.delete": "Διαγραφή",
+ "label.delete-account": "Διαγραφή λογαριασμού",
+ "label.delete-website": "Διαγραφή ιστότοπου",
+ "label.dismiss": "Dismiss",
"label.domain": "Τομέας",
+ "label.edit": "Επεξεργασία",
+ "label.edit-account": "Επεξεργασία λογαριασμού",
+ "label.edit-website": "Επεξεργασία ιστότοπου",
"label.enable-share-url": "Ενεργοποίηση κοινής χρήσης URL",
"label.invalid": "Μη έγκυρο",
"label.invalid-domain": "Μη έγκυρος τομέας",
"label.last-days": "Τελευταίες {x} ημέρες",
"label.last-hours": "Τελευταίες {x} ώρες",
"label.logged-in-as": "Συνδεθήκατε ως {username}",
+ "label.login": "Είσοδος",
"label.logout": "Αποσύνδεση",
+ "label.more": "Περισσότερα",
"label.name": "Όνομα",
"label.new-password": "Νέος κωδικός",
"label.password": "Κωδικός",
"label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
"label.profile": "Προφίλ",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Ανανέωση",
"label.required": "Απαιτείται",
+ "label.reset": "Επαναφορά",
+ "label.save": "Αποθήκευση",
"label.settings": "Ρυθμίσεις",
+ "label.share-url": "Κοινοποίηση διεύθυνσης URL",
+ "label.single-day": "Ημερήσια",
"label.this-month": "Αυτο το μήνα",
"label.this-week": "Αυτή την εβδομάδα",
"label.this-year": "Αυτή την χρονιά",
"label.timezone": "Ζώνη ώρας",
"label.today": "Σήμερα",
+ "label.tracking-code": "Κωδικός παρακολούθησης",
"label.unknown": "Άγνωστο",
"label.username": "Όνομα χρήστη",
+ "label.view-details": "Λεπτομέρειες",
"label.websites": "Ιστότοποι",
"message.active-users": "{x} ενεργοί {x, plural, one {επισκέπτης} other {επισκέπτες}}",
"message.confirm-delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {target};",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Λήψη κώδικα παρακολούθησης",
"message.go-to-settings": "Μεταβείτε στις ρυθμίσεις",
"message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.",
"message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Παραπομπές",
"metrics.unique-visitors": "Μοναδικοί επισκέπτες",
"metrics.views": "Προβολές",
- "metrics.visitors": "Επισκέπτες",
- "title.add-account": "Προσθήκη λογαριασμού",
- "title.add-website": "Προσθήκη ιστότοπου",
- "title.change-password": "Αλλαγή κωδικού",
- "title.delete-account": "Διαγραφή λογαριασμού",
- "title.delete-website": "Διαγραφή ιστότοπου",
- "title.edit-account": "Επεξεργασία λογαριασμού",
- "title.edit-website": "Επεξεργασία ιστότοπου",
- "title.share-url": "Κοινοποίηση διεύθυνσης URL",
- "title.tracking-code": "Κωδικός παρακολούθησης"
+ "metrics.visitors": "Επισκέπτες"
}
diff --git a/lang/en-US.json b/lang/en-US.json
index 7c513b95..5d3365b9 100644
--- a/lang/en-US.json
+++ b/lang/en-US.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Add account",
- "button.add-website": "Add website",
- "button.back": "Back",
- "button.cancel": "Cancel",
- "button.change-password": "Change password",
- "button.copy-to-clipboard": "Copy to clipboard",
- "button.date-range": "Date range",
- "button.delete": "Delete",
- "button.dismiss": "Dismiss",
- "button.edit": "Edit",
- "button.login": "Login",
- "button.more": "More",
- "button.refresh": "Refresh",
- "button.reset": "Reset",
- "button.save": "Save",
- "button.single-day": "Single day",
- "button.view-details": "View details",
"label.accounts": "Accounts",
+ "label.add-account": "Add account",
+ "label.add-website": "Add website",
"label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Back",
+ "label.cancel": "Cancel",
+ "label.change-password": "Change password",
"label.confirm-password": "Confirm password",
+ "label.copy-to-clipboard": "Copy to clipboard",
"label.current-password": "Current password",
"label.custom-range": "Custom range",
"label.dashboard": "Dashboard",
+ "label.date-range": "Date range",
"label.default-date-range": "Default date range",
+ "label.delete": "Delete",
+ "label.delete-account": "Delete account",
+ "label.delete-website": "Delete website",
+ "label.dismiss": "Dismiss",
"label.domain": "Domain",
+ "label.edit": "Edit",
+ "label.edit-account": "Edit account",
+ "label.edit-website": "Edit website",
"label.enable-share-url": "Enable share URL",
"label.invalid": "Invalid",
"label.invalid-domain": "Invalid domain",
"label.last-days": "Last {x} days",
"label.last-hours": "Last {x} hours",
"label.logged-in-as": "Logged in as {username}",
+ "label.login": "Login",
"label.logout": "Logout",
+ "label.more": "More",
"label.name": "Name",
"label.new-password": "New password",
"label.password": "Password",
"label.passwords-dont-match": "Passwords don't match",
"label.profile": "Profile",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Refresh",
"label.required": "Required",
+ "label.reset": "Reset",
+ "label.save": "Save",
"label.settings": "Settings",
+ "label.share-url": "Share URL",
+ "label.single-day": "Single day",
"label.this-month": "This month",
"label.this-week": "This week",
"label.this-year": "This year",
"label.timezone": "Timezone",
"label.today": "Today",
+ "label.tracking-code": "Tracking code",
"label.unknown": "Unknown",
"label.username": "Username",
+ "label.view-details": "View details",
"label.websites": "Websites",
"message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}",
"message.confirm-delete": "Are your sure you want to delete {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Get tracking code",
"message.go-to-settings": "Go to settings",
"message.incorrect-username-password": "Incorrect username/password.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "No data available.",
"message.no-websites-configured": "You don't have any websites configured.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Referrers",
"metrics.unique-visitors": "Unique visitors",
"metrics.views": "Views",
- "metrics.visitors": "Visitors",
- "title.add-account": "Add account",
- "title.add-website": "Add website",
- "title.change-password": "Change password",
- "title.delete-account": "Delete account",
- "title.delete-website": "Delete website",
- "title.edit-account": "Edit account",
- "title.edit-website": "Edit website",
- "title.share-url": "Share URL",
- "title.tracking-code": "Tracking code"
+ "metrics.visitors": "Visitors"
}
diff --git a/lang/es-MX.json b/lang/es-MX.json
index 16d911ea..4d57ac3c 100644
--- a/lang/es-MX.json
+++ b/lang/es-MX.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Agregar usuario",
- "button.add-website": "Agregar sitio",
- "button.back": "Atrás",
- "button.cancel": "Cancelar",
- "button.change-password": "Cambiar contraseña",
- "button.copy-to-clipboard": "Copiar al portapapeles",
- "button.date-range": "Date range",
- "button.delete": "Eliminar",
- "button.dismiss": "Dismiss",
- "button.edit": "Editar",
- "button.login": "Iniciar sesión",
- "button.more": "Más",
- "button.refresh": "Refresh",
- "button.reset": "Reset",
- "button.save": "Guardar",
- "button.single-day": "Single day",
- "button.view-details": "Ver detalles",
"label.accounts": "Usuarios",
+ "label.add-account": "Agregar usuario",
+ "label.add-website": "Agregar sitio",
"label.administrator": "Administrador",
+ "label.all": "Todos",
+ "label.all-websites": "Todos los sitios",
+ "label.back": "Atrás",
+ "label.cancel": "Cancelar",
+ "label.change-password": "Cambiar contraseña",
"label.confirm-password": "Confirmar contraseña",
+ "label.copy-to-clipboard": "Copiar al portapapeles",
"label.current-password": "Contraseña actual",
- "label.custom-range": "Custom range",
+ "label.custom-range": "Intervalo personalizado",
"label.dashboard": "Panel de control",
- "label.default-date-range": "Default date range",
+ "label.date-range": "Fechas",
+ "label.default-date-range": "Intervalo por defecto",
+ "label.delete": "Eliminar",
+ "label.delete-account": "Eliminar usuario",
+ "label.delete-website": "Eliminar sitio",
+ "label.dismiss": "Ignorar",
"label.domain": "Dominio",
+ "label.edit": "Editar",
+ "label.edit-account": "Editar usuario",
+ "label.edit-website": "Editar sitio",
"label.enable-share-url": "Habilitar compartir URL",
"label.invalid": "Inválido",
"label.invalid-domain": "Dominio inválido",
"label.last-days": "Últimos {x} días",
"label.last-hours": "Últimas {x} horas",
"label.logged-in-as": "Sesión iniciada como {username}",
+ "label.login": "Iniciar sesión",
"label.logout": "Cerrar sesión",
+ "label.more": "Más",
"label.name": "Nombre",
"label.new-password": "Nueva contraseña",
"label.password": "Contraseña",
"label.passwords-dont-match": "Las contraseñas no coinciden",
"label.profile": "Perfil",
+ "label.realtime": "Tiempo real",
+ "label.realtime-logs": "Registros en tiempo real",
+ "label.refresh": "Actualizar",
"label.required": "Requerido",
+ "label.reset": "Reiniciar",
+ "label.save": "Guardar",
"label.settings": "Configuraciones",
+ "label.share-url": "Compartir URL",
+ "label.single-day": "Dia",
"label.this-month": "Este mes",
"label.this-week": "Esta semana",
"label.this-year": "Este año",
- "label.timezone": "Timezone",
+ "label.timezone": "Zona horaria",
"label.today": "Hoy",
- "label.unknown": "Unknown",
+ "label.tracking-code": "Código de rastreo",
+ "label.unknown": "Desconocida",
"label.username": "Nombre de usuario",
+ "label.view-details": "Ver detalles",
"label.websites": "Sitios",
"message.active-users": "{x} {x, plural, one {activo} other {activos}}",
"message.confirm-delete": "¿Estás seguro(a) de querer eliminar {target}?",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "Obtener código de rastreo",
"message.go-to-settings": "Ir a la configuración",
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "Visitante desde {country} usando {browser} en {os} {device}",
+ "message.new-version-available": "Una nueva versíon de umami {version} esta disponible!",
"message.no-data-available": "Sin información disponible.",
"message.no-websites-configured": "No tienes ningún sitio configurado.",
"message.page-not-found": "Page not found",
@@ -72,7 +83,7 @@
"metrics.device.desktop": "Desktop",
"metrics.device.laptop": "Laptop",
"metrics.device.mobile": "Mobile",
- "metrics.device.tablet": "Tablet",
+ "metrics.device.tablet": "Tableta",
"metrics.devices": "Dispositivos",
"metrics.events": "Eventos",
"metrics.filter.combined": "Combinado",
@@ -84,14 +95,5 @@
"metrics.referrers": "Referentes",
"metrics.unique-visitors": "Visitantes únicos",
"metrics.views": "Vistas",
- "metrics.visitors": "Visitantes",
- "title.add-account": "Agregar usuario",
- "title.add-website": "Agregar sitio",
- "title.change-password": "Cambiar contraseña",
- "title.delete-account": "Eliminar usuario",
- "title.delete-website": "Eliminar sitio",
- "title.edit-account": "Editar usuario",
- "title.edit-website": "Editar sitio",
- "title.share-url": "Compartir URL",
- "title.tracking-code": "Código de rastreo"
+ "metrics.visitors": "Visitantes"
}
diff --git a/lang/fi-FI.json b/lang/fi-FI.json
new file mode 100644
index 00000000..1137c391
--- /dev/null
+++ b/lang/fi-FI.json
@@ -0,0 +1,99 @@
+{
+ "label.accounts": "Tilit",
+ "label.add-account": "Lisää tili",
+ "label.add-website": "Lisää verkkosivu",
+ "label.administrator": "Järjestelmänvalvoja",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Takaisin",
+ "label.cancel": "Peruuta",
+ "label.change-password": "Vaihda salasana",
+ "label.confirm-password": "Vahvista salasana",
+ "label.copy-to-clipboard": "Kopioi leikepöydälle",
+ "label.current-password": "Nykyinen salasana",
+ "label.custom-range": "Mukautettu jakso",
+ "label.dashboard": "Dashboard",
+ "label.date-range": "Ajanjakso",
+ "label.default-date-range": "Oletusajanjakso",
+ "label.delete": "Poista",
+ "label.delete-account": "Poista tili",
+ "label.delete-website": "Poista verkkosivu",
+ "label.dismiss": "Hylkää",
+ "label.domain": "Verkkotunnus",
+ "label.edit": "Muokkaa",
+ "label.edit-account": "Muokkaa tiliä",
+ "label.edit-website": "Muokkaa verkkosivua",
+ "label.enable-share-url": "Ota jakamisen URL-osoite käyttöön",
+ "label.invalid": "Virheellinen",
+ "label.invalid-domain": "Virheellinen verkkotunnus",
+ "label.last-days": "Viimeisimmät {x} päivät",
+ "label.last-hours": "Viimeisimmät {x} tunnit",
+ "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.password": "Salasana",
+ "label.passwords-dont-match": "Salasanat eivät täsmää",
+ "label.profile": "Profiili",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Päivitä",
+ "label.required": "Vaaditaan",
+ "label.reset": "Nollaa",
+ "label.save": "Tallenna",
+ "label.settings": "Asetukset",
+ "label.share-url": "Jaa URL",
+ "label.single-day": "Yksi päivä",
+ "label.this-month": "Tämä kuukausi",
+ "label.this-week": "Tämä viikko",
+ "label.this-year": "Tämä vuosi",
+ "label.timezone": "Aikavyöhyke",
+ "label.today": "Tänään",
+ "label.tracking-code": "Seurantakoodi",
+ "label.unknown": "Tuntematon",
+ "label.username": "Käyttäjänimi",
+ "label.view-details": "Katso tiedot",
+ "label.websites": "Verkkosivut",
+ "message.active-users": "{x} nykyinen {x, plural, yksi {visitor} muut {visitors}}",
+ "message.confirm-delete": "Haluatko varmasti poistaa {target}?",
+ "message.copied": "Kopioitu!",
+ "message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.",
+ "message.failure": "Jotain meni väärin.",
+ "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": "Visitor from {country} using {browser} on {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.save-success": "Tallennettu onnistuneesti.",
+ "message.share-url": "Tämä on julkisesti jaettu URL-osoitteelle {target}.",
+ "message.track-stats": "Jos haluat seurata kohteen {target} tilastoja, aseta seuraava koodi verkkosivustosi {head} osioon.",
+ "message.type-delete": "Kirjoita {delete} alla olevaan ruutuun vahvistaaksesi.",
+ "metrics.actions": "Toiminnat",
+ "metrics.average-visit-time": "Keskimääräinen vierailuaika",
+ "metrics.bounce-rate": "Välitön poistuminen",
+ "metrics.browsers": "Selaimet",
+ "metrics.countries": "Maat",
+ "metrics.device.desktop": "Pöytäkone",
+ "metrics.device.laptop": "Kannettava tietokone",
+ "metrics.device.mobile": "Mobiili",
+ "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.operating-systems": "Käyttöjärjestelmät",
+ "metrics.page-views": "Sivun näyttökertoja",
+ "metrics.pages": "Sivut",
+ "metrics.referrers": "Viittaajat",
+ "metrics.unique-visitors": "Uniikit vierailijat",
+ "metrics.views": "Näyttökertoja",
+ "metrics.visitors": "Vierailijat"
+}
diff --git a/lang/fo-FO.json b/lang/fo-FO.json
index db9e972b..e103ca47 100644
--- a/lang/fo-FO.json
+++ b/lang/fo-FO.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Ger brúkara",
- "button.add-website": "Legg heimasíðu til",
- "button.back": "Aftur",
- "button.cancel": "Strika",
- "button.change-password": "Broyt loyniorð",
- "button.copy-to-clipboard": "Kopier til clipboard",
- "button.date-range": "Vel dato",
- "button.delete": "Sletta",
- "button.dismiss": "Dismiss",
- "button.edit": "Ger broyting",
- "button.login": "Rita inn",
- "button.more": "Meira",
- "button.refresh": "Endurskapa",
- "button.reset": "Nulstilla",
- "button.save": "Goym",
- "button.single-day": "Einkultur dagur",
- "button.view-details": "Vís upplýsingar",
"label.accounts": "Brúkarar",
+ "label.add-account": "Ger brúkara",
+ "label.add-website": "Legg heimasíðu avtrat",
"label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Aftur",
+ "label.cancel": "Strika",
+ "label.change-password": "Skift loyniorð",
"label.confirm-password": "Vátta loyniorð",
+ "label.copy-to-clipboard": "Kopier til clipboard",
"label.current-password": "Núverandi loyniorð",
"label.custom-range": "Tillaga spenni",
"label.dashboard": "Yvirlitsskíggi",
+ "label.date-range": "Vel dato",
"label.default-date-range": "Standard dato",
+ "label.delete": "Sletta",
+ "label.delete-account": "Sletta brúkara",
+ "label.delete-website": "Sletta heimasíðu",
+ "label.dismiss": "Dismiss",
"label.domain": "Økisnavn",
+ "label.edit": "Ger broyting",
+ "label.edit-account": "Broyt brúkara",
+ "label.edit-website": "Broyt heimasíðu",
"label.enable-share-url": "Virkja deili leinki",
"label.invalid": "Ógilda",
"label.invalid-domain": "Ógilt økisnavn",
"label.last-days": "Seinastu {x} dagarnar",
"label.last-hours": "Seinastu {x} tímanar",
"label.logged-in-as": "Ritaður inn sum {username}",
+ "label.login": "Rita inn",
"label.logout": "Rita út",
+ "label.more": "Meira",
"label.name": "Navn",
"label.new-password": "Nýtt loyniorð",
"label.password": "Loyniorð",
"label.passwords-dont-match": "Loyniorðini eru ikki eins",
"label.profile": "Brúkari",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Endurskapa",
"label.required": "Krav",
+ "label.reset": "Nulstilla",
+ "label.save": "Goym",
"label.settings": "Stillingar",
+ "label.share-url": "Deil leinku",
+ "label.single-day": "Einkultur dagur",
"label.this-month": "Hendan mánan",
"label.this-week": "Hesa vikuna",
"label.this-year": "Hetta árið",
"label.timezone": "Tíðarsona",
"label.today": "Í dag",
+ "label.tracking-code": "Spori kota",
"label.unknown": "Ókent",
"label.username": "Brúkaranavn",
+ "label.view-details": "Vís upplýsingar",
"label.websites": "Heimasíður",
"message.active-users": "{x} í løtuni {x, plural, one {vitjandi} other { vitjandi }}",
"message.confirm-delete": "Ert tú sikkur at tú ynskir at sletta {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Fá sporings kotu",
"message.go-to-settings": "Far til stillingar",
"message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Einki data tøk.",
"message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Framsendingar",
"metrics.unique-visitors": "Einsýna vitjanir",
"metrics.views": "Vitjanir",
- "metrics.visitors": "Vitjandi",
- "title.add-account": "Ger brúkara",
- "title.add-website": "Legg heimasíðu avtrat",
- "title.change-password": "Skift loyniorð",
- "title.delete-account": "Sletta brúkara",
- "title.delete-website": "Sletta heimasíðu",
- "title.edit-account": "Broyt brúkara",
- "title.edit-website": "Broyt heimasíðu",
- "title.share-url": "Deil leinku",
- "title.tracking-code": "Spori kota"
+ "metrics.visitors": "Vitjandi"
}
diff --git a/lang/fr-FR.json b/lang/fr-FR.json
index 762cae34..622c760f 100644
--- a/lang/fr-FR.json
+++ b/lang/fr-FR.json
@@ -1,53 +1,63 @@
{
- "button.add-account": "Ajouter un compte",
- "button.add-website": "Ajouter un site",
- "button.back": "Retour",
- "button.cancel": "Annuler",
- "button.change-password": "Changer de mot de passse",
- "button.copy-to-clipboard": "Copier dans le presse papier",
- "button.date-range": "Date range",
- "button.delete": "Supprimer",
- "button.dismiss": "Dismiss",
- "button.edit": "Modifier",
- "button.login": "Connexion",
- "button.more": "Plus",
- "button.refresh": "Refresh",
- "button.reset": "Reset",
- "button.save": "Sauvegarder",
- "button.single-day": "Single day",
- "button.view-details": "Voir les details",
"label.accounts": "Comptes",
+ "label.add-account": "Ajouter un compte",
+ "label.add-website": "Ajouter un site",
"label.administrator": "Administrateur",
+ "label.all": "Tout",
+ "label.all-websites": "Tous les sites web",
+ "label.back": "Retour",
+ "label.cancel": "Annuler",
+ "label.change-password": "Changer le mot de passe",
"label.confirm-password": "Confirmation du mot de passe",
+ "label.copy-to-clipboard": "Copier dans le presse papier",
"label.current-password": "Mot de passe actuel",
- "label.custom-range": "Plage personnalisée",
+ "label.custom-range": "Intervalle personnalisé",
"label.dashboard": "Tableau de bord",
- "label.default-date-range": "Default date range",
+ "label.date-range": "Intervalle",
+ "label.default-date-range": "Intervalle par défaut",
+ "label.delete": "Supprimer",
+ "label.delete-account": "Supprimer le compte",
+ "label.delete-website": "Supprimer le site",
+ "label.dismiss": "Ignorer",
"label.domain": "Domaine",
+ "label.edit": "Modifier",
+ "label.edit-account": "Modifier le compte",
+ "label.edit-website": "Modifier le site",
"label.enable-share-url": "Activer le partage d'URL",
"label.invalid": "Invalide",
"label.invalid-domain": "Domaine invalide",
"label.last-days": "{x} derniers jours",
"label.last-hours": "{x} dernières heures",
"label.logged-in-as": "Connecté en tant que {username}",
+ "label.login": "Connexion",
"label.logout": "Déconnexion",
+ "label.more": "Plus",
"label.name": "Nom",
"label.new-password": "Nouveau mot de passe",
"label.password": "Mot de passe",
"label.passwords-dont-match": "Les mots de passe ne correspondent pas",
- "label.profile": "Profile",
+ "label.profile": "Profil",
+ "label.realtime": "Temps réel",
+ "label.realtime-logs": "Logs en temps réel",
+ "label.refresh": "Rafraîchir",
"label.required": "Requis",
+ "label.reset": "Réinitialiser",
+ "label.save": "Sauvegarder",
"label.settings": "Paramètres",
+ "label.share-url": "Partager l'URL",
+ "label.single-day": "Journée",
"label.this-month": "Ce mois ci",
"label.this-week": "Cette semaine",
"label.this-year": "Cette année",
- "label.timezone": "Timezone",
+ "label.timezone": "Fuseau horaire",
"label.today": "Aujourd'hui",
- "label.unknown": "Unknown",
+ "label.tracking-code": "Code de suivi",
+ "label.unknown": "Inconnu",
"label.username": "Nom d'utilisateur",
+ "label.view-details": "Voir les details",
"label.websites": "Sites",
"message.active-users": "{x} {x, plural, one {visiteur} other {visiteurs}} actuellement",
- "message.confirm-delete": "Êtes-vous sur de vouloir supprimer {target}?",
+ "message.confirm-delete": "Êtes-vous sûr de vouloir supprimer {target} ?",
"message.copied": "Copié !",
"message.delete-warning": "Toutes les données associées seront également supprimées.",
"message.failure": "Un problème est survenu.",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "Obtenez le code de suivi",
"message.go-to-settings": "Aller aux paramètres",
"message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "Visiteur de {country} utilisant {browser} sur {os} {device}",
+ "message.new-version-available": "Une nouvelle version de umami {version} est disponible !",
"message.no-data-available": "Pas de données disponibles.",
"message.no-websites-configured": "Vous n'avez configuré aucun site Web.",
"message.page-not-found": "Page non trouvée.",
@@ -69,10 +80,10 @@
"metrics.bounce-rate": "Taux de rebond",
"metrics.browsers": "Navigateurs",
"metrics.countries": "Pays",
- "metrics.device.desktop": "Desktop",
- "metrics.device.laptop": "Laptop",
- "metrics.device.mobile": "Mobile",
- "metrics.device.tablet": "Tablet",
+ "metrics.device.desktop": "Ordinateur",
+ "metrics.device.laptop": "Portable",
+ "metrics.device.mobile": "Téléphone",
+ "metrics.device.tablet": "Tablette",
"metrics.devices": "Appareils",
"metrics.events": "Événements",
"metrics.filter.combined": "Combiné",
@@ -84,14 +95,5 @@
"metrics.referrers": "URL Référentes",
"metrics.unique-visitors": "Visiteurs uniques",
"metrics.views": "Vues",
- "metrics.visitors": "Visiteurs",
- "title.add-account": "Ajouter un compte",
- "title.add-website": "Ajouter un site",
- "title.change-password": "Changer le mot de passe",
- "title.delete-account": "Supprimer le compte",
- "title.delete-website": "Suprimer le site",
- "title.edit-account": "Modifier le compte",
- "title.edit-website": "Modifier le site",
- "title.share-url": "Partager l'URL",
- "title.tracking-code": "Code de suivi"
+ "metrics.visitors": "Visiteurs"
}
diff --git a/lang/id-ID.json b/lang/id-ID.json
new file mode 100644
index 00000000..99fd0fe5
--- /dev/null
+++ b/lang/id-ID.json
@@ -0,0 +1,99 @@
+{
+ "label.accounts": "Akun",
+ "label.add-account": "Tambah akun",
+ "label.add-website": "Tambah situs web",
+ "label.administrator": "Pengelola",
+ "label.all": "Semua",
+ "label.all-websites": "Semua website",
+ "label.back": "Kembali",
+ "label.cancel": "Batal",
+ "label.change-password": "Ganti kata sandi",
+ "label.confirm-password": "Konfirmasi kata sandi",
+ "label.copy-to-clipboard": "Salin ke papan klip",
+ "label.current-password": "Kata sandi sekarang",
+ "label.custom-range": "Rentang khusus",
+ "label.dashboard": "Dasbor",
+ "label.date-range": "Rentang tanggal",
+ "label.default-date-range": "Rentang tanggal bawaan",
+ "label.delete": "Hapus",
+ "label.delete-account": "Hapus akun",
+ "label.delete-website": "Hapus situs web",
+ "label.dismiss": "Tutup",
+ "label.domain": "Domain",
+ "label.edit": "Sunting",
+ "label.edit-account": "Sunting akun",
+ "label.edit-website": "Sunting situs web",
+ "label.enable-share-url": "Aktifkan URL berbagi",
+ "label.invalid": "Tidak valid",
+ "label.invalid-domain": "Domain tidak valid",
+ "label.last-days": "{x} hari terakhir",
+ "label.last-hours": "{x} jam terakhir",
+ "label.logged-in-as": "Masuk sebagai {username}",
+ "label.login": "Masuk",
+ "label.logout": "Keluar",
+ "label.more": "Lebih banyak",
+ "label.name": "Nama",
+ "label.new-password": "Kata sandi baru",
+ "label.password": "Kata sandi",
+ "label.passwords-dont-match": "Kata sandi tidak cocok",
+ "label.profile": "Profil",
+ "label.realtime": "Waktu nyata",
+ "label.realtime-logs": "Log waktu nyata",
+ "label.refresh": "Segarkan",
+ "label.required": "Wajib",
+ "label.reset": "Atur ulang",
+ "label.save": "Simpan",
+ "label.settings": "Pengaturan",
+ "label.share-url": "Bagikan URL",
+ "label.single-day": "Sehari",
+ "label.this-month": "Bulan ini",
+ "label.this-week": "Minggu ini",
+ "label.this-year": "Tahun ini",
+ "label.timezone": "Zona waktu",
+ "label.today": "Hari ini",
+ "label.tracking-code": "Kode lacak",
+ "label.unknown": "Tidak diketahui",
+ "label.username": "Nama pengguna",
+ "label.view-details": "Lihat Detil",
+ "label.websites": "Situs web",
+ "message.active-users": "{x} pengunjung saat ini",
+ "message.confirm-delete": "Apakah kamu yakin ingin menghapus {target}?",
+ "message.copied": "Tersalin!",
+ "message.delete-warning": "Semua data terkait juga akan dihapus.",
+ "message.failure": "Ada yang salah.",
+ "message.get-share-url": "Dapatkan URL berbagi",
+ "message.get-tracking-code": "Dapatkan kode pelacakan",
+ "message.go-to-settings": "Pergi ke pengaturan",
+ "message.incorrect-username-password": "Nama pengguna/kata sandi salah.",
+ "message.log.visitor": "Pengunjung dari {country} dengan {browser} di {device} {os}",
+ "message.new-version-available": "Versi terbaru umami {version} telah tersedia!",
+ "message.no-data-available": "Tidak ada data.",
+ "message.no-websites-configured": "Anda tidak memiliki situs web yang dikonfigurasi.",
+ "message.page-not-found": "Halaman tidak ditemukan.",
+ "message.powered-by": "Didukung oleh {name}",
+ "message.save-success": "Berhasil disimpan.",
+ "message.share-url": "Ini adalah URL yang dibagikan secara publik untuk {target}.",
+ "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.",
+ "metrics.actions": "Aksi",
+ "metrics.average-visit-time": "Waktu kunjungan rata-rata",
+ "metrics.bounce-rate": "Rasio pentalan",
+ "metrics.browsers": "Peramban",
+ "metrics.countries": "Negara",
+ "metrics.device.desktop": "Desktop",
+ "metrics.device.laptop": "Laptop",
+ "metrics.device.mobile": "Ponsel",
+ "metrics.device.tablet": "Tablet",
+ "metrics.devices": "Perangkat",
+ "metrics.events": "Perihal",
+ "metrics.filter.combined": "Gabungan",
+ "metrics.filter.domain-only": "Hanya domain",
+ "metrics.filter.raw": "Mentah",
+ "metrics.operating-systems": "Sistem Operasi",
+ "metrics.page-views": "Tampilan halaman",
+ "metrics.pages": "Halaman",
+ "metrics.referrers": "Perujuk",
+ "metrics.unique-visitors": "Pengunjung unik",
+ "metrics.views": "Tampilan",
+ "metrics.visitors": "Pengunjung"
+}
diff --git a/lang/ja-JP.json b/lang/ja-JP.json
index 51cdaec1..388837a4 100644
--- a/lang/ja-JP.json
+++ b/lang/ja-JP.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "アカウントを追加する",
- "button.add-website": "Webサイトを追加する",
- "button.back": "戻る",
- "button.cancel": "キャンセル",
- "button.change-password": "パスワード変更",
- "button.copy-to-clipboard": "クリップボードにコピー",
- "button.date-range": "日付範囲",
- "button.delete": "削除",
- "button.dismiss": "無視する",
- "button.edit": "編集",
- "button.login": "ログイン",
- "button.more": "さらに表示",
- "button.refresh": "更新",
- "button.reset": "リセット",
- "button.save": "保存",
- "button.single-day": "一日のみ",
- "button.view-details": "詳細を見る",
"label.accounts": "アカウント",
+ "label.add-account": "アカウントの追加",
+ "label.add-website": "Webサイトの追加",
"label.administrator": "管理者",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "戻る",
+ "label.cancel": "キャンセル",
+ "label.change-password": "パスワード変更",
"label.confirm-password": "パスワード(確認)",
+ "label.copy-to-clipboard": "クリップボードにコピー",
"label.current-password": "現在のパスワード",
"label.custom-range": "期間を指定する",
"label.dashboard": "ダッシュボード",
+ "label.date-range": "日付範囲",
"label.default-date-range": "最初に表示する期間",
+ "label.delete": "削除",
+ "label.delete-account": "アカウントの削除",
+ "label.delete-website": "Webサイトの削除",
+ "label.dismiss": "無視する",
"label.domain": "ドメイン",
+ "label.edit": "編集",
+ "label.edit-account": "アカウントの編集",
+ "label.edit-website": "Webサイトの編集",
"label.enable-share-url": "共有リンクを有効にする",
"label.invalid": "無効",
"label.invalid-domain": "無効なドメイン",
"label.last-days": "過去{x}日間",
"label.last-hours": "過去{x}時間",
"label.logged-in-as": "{username}でログイン中",
+ "label.login": "ログイン",
"label.logout": "ログアウト",
+ "label.more": "さらに表示",
"label.name": "名前",
"label.new-password": "新しいパスワード",
"label.password": "パスワード",
"label.passwords-dont-match": "パスワードが一致しません",
"label.profile": "プロファイル",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "更新",
"label.required": "必須",
+ "label.reset": "リセット",
+ "label.save": "保存",
"label.settings": "設定",
+ "label.share-url": "共有リンク",
+ "label.single-day": "一日のみ",
"label.this-month": "今月",
"label.this-week": "今週",
"label.this-year": "今年",
"label.timezone": "タイムゾーン",
"label.today": "今日",
+ "label.tracking-code": "トラッキングコード",
"label.unknown": "不明",
"label.username": "ユーザー名",
+ "label.view-details": "詳細を見る",
"label.websites": "Webサイト",
"message.active-users": "{x}人が閲覧中です。",
"message.confirm-delete": "{target}を削除してもよろしいですか?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "トラッキングコードを取得",
"message.go-to-settings": "設定する",
"message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "新しいバージョン({version})が利用可能です!",
"message.no-data-available": "データがありません。",
"message.no-websites-configured": "Webサイトが設定されていません。",
@@ -84,14 +95,5 @@
"metrics.referrers": "リファラー",
"metrics.unique-visitors": "ユニーク訪問者数",
"metrics.views": "閲覧数",
- "metrics.visitors": "訪問者数",
- "title.add-account": "アカウントの追加",
- "title.add-website": "Webサイトの追加",
- "title.change-password": "パスワード変更",
- "title.delete-account": "アカウントの削除",
- "title.delete-website": "Webサイトの削除",
- "title.edit-account": "アカウントの編集",
- "title.edit-website": "Webサイトの編集",
- "title.share-url": "共有リンク",
- "title.tracking-code": "トラッキングコード"
+ "metrics.visitors": "訪問者数"
}
diff --git a/lang/mn-MN.json b/lang/mn-MN.json
index 694e068e..f40f30f1 100644
--- a/lang/mn-MN.json
+++ b/lang/mn-MN.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Хэрэглэгч нэмэх",
- "button.add-website": "Веб нэмэх",
- "button.back": "Буцах",
- "button.cancel": "Цуцлах",
- "button.change-password": "Нууц үг солих",
- "button.copy-to-clipboard": "Хуулах",
- "button.date-range": "Хугацааны мужид",
- "button.delete": "Устгах",
- "button.dismiss": "Үл хэргэсэх",
- "button.edit": "Засах",
- "button.login": "Нэвтрэх",
- "button.more": "Цааш",
- "button.refresh": "Сэргээх",
- "button.reset": "Хуучин хэвд нь оруулах",
- "button.save": "Хадгалах",
- "button.single-day": "Нэг өдөр",
- "button.view-details": "Дэлгэрүүлж харах",
"label.accounts": "Хэрэглэгчид",
+ "label.add-account": "Хэрэглэгч нэмэх",
+ "label.add-website": "Веб нэмэх",
"label.administrator": "Админ",
+ "label.all": "Бүх",
+ "label.all-websites": "Бүх вебүүд",
+ "label.back": "Буцах",
+ "label.cancel": "Цуцлах",
+ "label.change-password": "Нууц үг солих",
"label.confirm-password": "Шинэ нууц үгээ давтах",
+ "label.copy-to-clipboard": "Хуулах",
"label.current-password": "Ашиглаж буй нууц үг",
"label.custom-range": "Дурын хугацаа",
"label.dashboard": "Хянах самбар",
+ "label.date-range": "Хугацааны мужид",
"label.default-date-range": "Өгөгдмөл хугацааны муж",
+ "label.delete": "Устгах",
+ "label.delete-account": "Хэрэглэгч устгах",
+ "label.delete-website": "Веб устгах",
+ "label.dismiss": "Үл хэргэсэх",
"label.domain": "Домэйн",
+ "label.edit": "Засах",
+ "label.edit-account": "Хэрэглэгч засах",
+ "label.edit-website": "Веб засах",
"label.enable-share-url": "Хуваалцах холбоос идэвхжүүлэх",
"label.invalid": "Буруу",
"label.invalid-domain": "Буруу домэйн",
"label.last-days": "Сүүлийн {x} хоног",
"label.last-hours": "Сүүлийн {x} цаг",
"label.logged-in-as": "{username}-р нэвтэрсэн",
+ "label.login": "Нэвтрэх",
"label.logout": "Гарах",
+ "label.more": "Цааш",
"label.name": "Нэр",
"label.new-password": "Шинэ нууц үг",
"label.password": "Нууц үг",
"label.passwords-dont-match": "Нууц үг тохирохгүй байна",
"label.profile": "Бүртгэл",
+ "label.realtime": "Яг одоо",
+ "label.realtime-logs": "Бодит хугацааны бүртгэл",
+ "label.refresh": "Сэргээх",
"label.required": "Шаардлагатай",
+ "label.reset": "Хуучин хэвд нь оруулах",
+ "label.save": "Хадгалах",
"label.settings": "Тохиргоо",
+ "label.share-url": "Хуваалцах холбоос",
+ "label.single-day": "Нэг өдөр",
"label.this-month": "Энэ сар",
"label.this-week": "Энэ долоо хоног",
"label.this-year": "Энэ жил",
"label.timezone": "Цагийн бүс",
"label.today": "Өнөөдөр",
+ "label.tracking-code": "Мөрдөх код",
"label.unknown": "Тодорхойгүй",
"label.username": "Хэрэглэгчийн нэр",
+ "label.view-details": "Дэлгэрүүлж харах",
"label.websites": "Вебүүд",
"message.active-users": "одоо {x} {x, plural, one {зочин} other {зочин}} байна",
"message.confirm-delete": "Та {target}-г устгахдаа итгэлтэй байна уу?",
@@ -55,11 +65,12 @@
"message.get-tracking-code": "Мөрдөх код авах",
"message.go-to-settings": "Тохиргоо руу очих",
"message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.",
+ "message.log.visitor": "{country} улсаас {os} {device} дээр {browser} хөтөч ашиглан орсон",
"message.new-version-available": "Umami-гийн шинэ хувилбар {version} гарсан байна!",
"message.no-data-available": "Өгөгдөл алга.",
"message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.",
"message.page-not-found": "Хуудас олдсонгүй.",
- "message.powered-by": "Powered by {name}",
+ "message.powered-by": "{name} дээр суурилсан",
"message.save-success": "Амжилттай хадгаллаа.",
"message.share-url": "{target}-г нийтэд хуваалцах холбоос.",
"message.track-stats": "{target} вебийн статистикийг бүртгэхийн тулд доорх кодыг вебийнхээ {head} хэсэгт байрлуулна уу.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Чиглүүлэгч",
"metrics.unique-visitors": "Зочид",
"metrics.views": "Үзсэн",
- "metrics.visitors": "Зочид",
- "title.add-account": "Хэрэглэгч нэмэх",
- "title.add-website": "Веб нэмэх",
- "title.change-password": "Нууц үг солих",
- "title.delete-account": "Хэрэглэгч устгах",
- "title.delete-website": "Веб устгах",
- "title.edit-account": "Хэрэглэгч засах",
- "title.edit-website": "Веб засах",
- "title.share-url": "Хуваалцах холбоос",
- "title.tracking-code": "Мөрдөх код"
+ "metrics.visitors": "Зочид"
}
diff --git a/lang/nb-NO.json b/lang/nb-NO.json
new file mode 100644
index 00000000..6cc8af0c
--- /dev/null
+++ b/lang/nb-NO.json
@@ -0,0 +1,99 @@
+{
+ "label.accounts": "Kontoer",
+ "label.add-account": "Legg til konto",
+ "label.add-website": "Legg til nettsted",
+ "label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Tilbake",
+ "label.cancel": "Avvis",
+ "label.change-password": "Bytt passord",
+ "label.confirm-password": "Godkjenn passord",
+ "label.copy-to-clipboard": "Kopier til utklippstavle",
+ "label.current-password": "Nåværende passord",
+ "label.custom-range": "Egendefinert utvalg",
+ "label.dashboard": "Dashboard",
+ "label.date-range": "Datointervall",
+ "label.default-date-range": "Standard datoperiode",
+ "label.delete": "Slett",
+ "label.delete-account": "Slett konto",
+ "label.delete-website": "Slett nettstedet",
+ "label.dismiss": "Avbryt",
+ "label.domain": "Domene",
+ "label.edit": "Rediger",
+ "label.edit-account": "Rediger konto",
+ "label.edit-website": "Rediger nettsted",
+ "label.enable-share-url": "Aktiver delings-URL",
+ "label.invalid": "Ugyldig",
+ "label.invalid-domain": "Ugyldig domene",
+ "label.last-days": "Siste {x} dager",
+ "label.last-hours": "Siste {x} timer",
+ "label.logged-in-as": "Logget på som {brukernavn}",
+ "label.login": "Logg inn",
+ "label.logout": "Logg ut",
+ "label.more": "Mer",
+ "label.name": "Navn",
+ "label.new-password": "Nytt passord",
+ "label.password": "Passord",
+ "label.passwords-dont-match": "Passordene er ikke like",
+ "label.profile": "Profil",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Oppdater",
+ "label.required": "Påkrevd",
+ "label.reset": "Nullstill",
+ "label.save": "Lagre",
+ "label.settings": "Innstillinger",
+ "label.share-url": "Del URL",
+ "label.single-day": "Enkelt dag",
+ "label.this-month": "Denne måneden",
+ "label.this-week": "Denne uka",
+ "label.this-year": "I år",
+ "label.timezone": "Tidssone",
+ "label.today": "I dag",
+ "label.tracking-code": "Sporingskode",
+ "label.unknown": "Ukjent",
+ "label.username": "Brukernavn",
+ "label.view-details": "Vis detaljer",
+ "label.websites": "Nettsteder",
+ "message.active-users": "{x} {x, plural, one {besøkende} other {besøkende}} nå",
+ "message.confirm-delete": "Er du sikker på at du vil slette {target}?",
+ "message.copied": "Kopiert!",
+ "message.delete-warning": "Alle tilknyttede data slettes også.",
+ "message.failure": "Noe gikk galt.",
+ "message.get-share-url": "Få delings-URL",
+ "message.get-tracking-code": "Få sporingskode",
+ "message.go-to-settings": "Gå til innstillinger",
+ "message.incorrect-username-password": "Ugyldig brukernavn/passord.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
+ "message.new-version-available": "En ny versjon av umami {version} er tilgjengelig!",
+ "message.no-data-available": "Ingen data tilgjengelig.",
+ "message.no-websites-configured": "Du har ikke satt opp noen nettsteder.",
+ "message.page-not-found": "Side ikke funnet.",
+ "message.powered-by": "Drevet av {name}",
+ "message.save-success": "Lagret!",
+ "message.share-url": "Dette er den offentlige delings-URL-en for {target}.",
+ "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.",
+ "metrics.actions": "Handlinger",
+ "metrics.average-visit-time": "Gjennomsnittlig besøkelsestid",
+ "metrics.bounce-rate": "Avvisningsfrekvens",
+ "metrics.browsers": "Nettlesere",
+ "metrics.countries": "Land",
+ "metrics.device.desktop": "Desktop",
+ "metrics.device.laptop": "Laptop",
+ "metrics.device.mobile": "Mobiltelefon",
+ "metrics.device.tablet": "Nettbrett",
+ "metrics.devices": "Enheter",
+ "metrics.events": "Arrangementer",
+ "metrics.filter.combined": "Kombinert",
+ "metrics.filter.domain-only": "Bare domene",
+ "metrics.filter.raw": "Rå",
+ "metrics.operating-systems": "Operativsystemer",
+ "metrics.page-views": "Sidevisninger",
+ "metrics.pages": "Sider",
+ "metrics.referrers": "Referanser",
+ "metrics.unique-visitors": "Unike besøkende",
+ "metrics.views": "Visninger",
+ "metrics.visitors": "Besøkende"
+}
diff --git a/lang/nl-NL.json b/lang/nl-NL.json
index 148fd32e..518ffaa8 100644
--- a/lang/nl-NL.json
+++ b/lang/nl-NL.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Account toevoegen",
- "button.add-website": "Website toevoegen",
- "button.back": "Terug",
- "button.cancel": "Annuleren",
- "button.change-password": "Wachtwoord wijzigen",
- "button.copy-to-clipboard": "Kopiëer naar klembord",
- "button.date-range": "Datumbereik",
- "button.delete": "Verwijderen",
- "button.dismiss": "Dismiss",
- "button.edit": "Bewerken",
- "button.login": "Inloggen",
- "button.more": "Toon meer",
- "button.refresh": "Vernieuwen",
- "button.reset": "Reset",
- "button.save": "Opslaan",
- "button.single-day": "Enkele dag",
- "button.view-details": "Meer details",
- "label.accounts": "Accounts",
+ "label.accounts": "Gebruikers",
+ "label.add-account": "Account toevoegen",
+ "label.add-website": "Website toevoegen",
"label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Terug",
+ "label.cancel": "Annuleren",
+ "label.change-password": "Wachtwoord wijzigen",
"label.confirm-password": "Wachtwoord bevestigen",
+ "label.copy-to-clipboard": "Kopiëer naar klembord",
"label.current-password": "Huidig wachtwoord",
"label.custom-range": "Aangepast bereik",
- "label.dashboard": "Dashboard",
- "label.default-date-range": "Default date range",
+ "label.dashboard": "Overzicht",
+ "label.date-range": "Datumbereik",
+ "label.default-date-range": "Standaard bereik",
+ "label.delete": "Verwijderen",
+ "label.delete-account": "Account verwijderen",
+ "label.delete-website": "Website verwijderen",
+ "label.dismiss": "Negeren",
"label.domain": "Domein",
+ "label.edit": "Bewerken",
+ "label.edit-account": "Account bewerken",
+ "label.edit-website": "Website bewerken",
"label.enable-share-url": "Sta delen via openbare URL toe",
"label.invalid": "Ongeldig",
"label.invalid-domain": "Ongeldig domein",
"label.last-days": "Laatste {x} dagen",
"label.last-hours": "Laatste {x} uur",
"label.logged-in-as": "Ingelogd als {username}",
+ "label.login": "Inloggen",
"label.logout": "Uitloggen",
+ "label.more": "Toon meer",
"label.name": "Naam",
"label.new-password": "Nieuw wachtwoord",
"label.password": "Wachtwoord",
"label.passwords-dont-match": "Wachtwoorden komen niet overeen",
"label.profile": "Profiel",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Vernieuwen",
"label.required": "Verplicht",
+ "label.reset": "Resetten",
+ "label.save": "Opslaan",
"label.settings": "Instellingen",
+ "label.share-url": "URL delen",
+ "label.single-day": "Enkele dag",
"label.this-month": "Deze maand",
"label.this-week": "Deze week",
"label.this-year": "Dit jaar",
- "label.timezone": "Timezone",
+ "label.timezone": "Tijdzone",
"label.today": "Vandaag",
+ "label.tracking-code": "Tracking code",
"label.unknown": "Onbekend",
"label.username": "Gebruikersnaam",
+ "label.view-details": "Meer details",
"label.websites": "Websites",
"message.active-users": "{x} actieve {x, plural, one {bezoeker} other {bezoekers}}",
"message.confirm-delete": "Weet je zeker dat je {target} wilt verwijderen?",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "Tracking code",
"message.go-to-settings": "Naar instellingen",
"message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
+ "message.new-version-available": "Een nieuwe versie van umami {version} is beschikbaar!",
"message.no-data-available": "Geen gegevens beschikbaar.",
"message.no-websites-configured": "Je hebt geen websites ingesteld.",
"message.page-not-found": "Pagina niet gevonden.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Verwijzers",
"metrics.unique-visitors": "Unieke bezoekers",
"metrics.views": "Weergaven",
- "metrics.visitors": "Bezoekers",
- "title.add-account": "Account toevoegen",
- "title.add-website": "Website toevoegen",
- "title.change-password": "Wachtwoord wijzigen",
- "title.delete-account": "Account verwijderen",
- "title.delete-website": "Website verwijderen",
- "title.edit-account": "Account bewerken",
- "title.edit-website": "Website bewerken",
- "title.share-url": "URL delen",
- "title.tracking-code": "Tracking code"
+ "metrics.visitors": "Bezoekers"
}
diff --git a/lang/pt-PT.json b/lang/pt-PT.json
index db9a8a7f..05cf5396 100644
--- a/lang/pt-PT.json
+++ b/lang/pt-PT.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Adicionar conta",
- "button.add-website": "Adicionar website",
- "button.back": "Voltar",
- "button.cancel": "Cancelar",
- "button.change-password": "Alterar palavra-passe",
- "button.copy-to-clipboard": "Copiar para a área de transferência",
- "button.date-range": "Intervalo de datas",
- "button.delete": "Eliminar",
- "button.dismiss": "Ignorar",
- "button.edit": "Editar",
- "button.login": "Iniciar sessão",
- "button.more": "Mais",
- "button.refresh": "Atualizar",
- "button.reset": "Repor",
- "button.save": "Guardar",
- "button.single-day": "Dia único",
- "button.view-details": "Ver detalhes",
"label.accounts": "Contas",
+ "label.add-account": "Adicionar conta",
+ "label.add-website": "Adicionar website",
"label.administrator": "Administrador",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Voltar",
+ "label.cancel": "Cancelar",
+ "label.change-password": "Alterar palavra-passe",
"label.confirm-password": "Confirmar palavra-passe",
+ "label.copy-to-clipboard": "Copiar para a área de transferência",
"label.current-password": "Palavra-passe atual",
"label.custom-range": "Intervalo personalizado",
"label.dashboard": "Dashboard",
+ "label.date-range": "Intervalo de datas",
"label.default-date-range": "Intervalo de datas predefinido",
+ "label.delete": "Eliminar",
+ "label.delete-account": "Eliminar conta",
+ "label.delete-website": "Eliminar website",
+ "label.dismiss": "Ignorar",
"label.domain": "Domínio",
+ "label.edit": "Editar",
+ "label.edit-account": "Editar conta",
+ "label.edit-website": "Editar website",
"label.enable-share-url": "Ativar link de partilha",
"label.invalid": "Inválido",
"label.invalid-domain": "Domínio inválido",
"label.last-days": "Últimos {x} dias",
"label.last-hours": "Últimas {x} horas",
"label.logged-in-as": "Sessão iniciada como {username}",
+ "label.login": "Iniciar sessão",
"label.logout": "Sair",
+ "label.more": "Mais",
"label.name": "Nome",
"label.new-password": "Nova palavra-passe",
"label.password": "Palavra-passe",
"label.passwords-dont-match": "Palavra-passes não correspondem",
"label.profile": "Perfil",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Atualizar",
"label.required": "Obrigatório",
+ "label.reset": "Repor",
+ "label.save": "Guardar",
"label.settings": "Definições",
+ "label.share-url": "Partilhar link",
+ "label.single-day": "Dia único",
"label.this-month": "Este mês",
"label.this-week": "Esta semana",
"label.this-year": "Este ano",
"label.timezone": "Fuso horário",
"label.today": "Hoje",
+ "label.tracking-code": "Código de tracking",
"label.unknown": "Desconhecido",
"label.username": "Nome de utilizador",
+ "label.view-details": "Ver detalhes",
"label.websites": "Websites",
"message.active-users": "{x} {x, plural, one {visitante} other {visitantes}} neste momento",
"message.confirm-delete": "Tens a certeza que queres eliminar {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Obter código de tracking",
"message.go-to-settings": "Ir para as definições",
"message.incorrect-username-password": "Nome de utilizador/palavra-passe incorretos.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "Uma nova versão de umami {version} está disponível!",
"message.no-data-available": "Sem dados disponíveis.",
"message.no-websites-configured": "Não tens nenhum website configurado.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Referrers",
"metrics.unique-visitors": "Visitantes únicos",
"metrics.views": "Visualizações",
- "metrics.visitors": "Visitantes",
- "title.add-account": "Adicionar conta",
- "title.add-website": "Adicionar website",
- "title.change-password": "Alterar palavra-passe",
- "title.delete-account": "Eliminar conta",
- "title.delete-website": "Eliminar website",
- "title.edit-account": "Editar conta",
- "title.edit-website": "Editar website",
- "title.share-url": "Partilhar link",
- "title.tracking-code": "Código de tracking"
+ "metrics.visitors": "Visitantes"
}
diff --git a/lang/ro-RO.json b/lang/ro-RO.json
index edaf27d6..9ef15254 100644
--- a/lang/ro-RO.json
+++ b/lang/ro-RO.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Adaugă cont",
- "button.add-website": "Adaugă site web",
- "button.back": "Înapoi",
- "button.cancel": "Anulează",
- "button.change-password": "Schimbă parola",
- "button.copy-to-clipboard": "Copiază în clipboard",
- "button.date-range": "Interval de date",
- "button.delete": "Șterge",
- "button.dismiss": "Renunță",
- "button.edit": "Editare",
- "button.login": "Autentificare",
- "button.more": "Mai mult",
- "button.refresh": "Reîmprospătare",
- "button.reset": "Resetează",
- "button.save": "Salvează",
- "button.single-day": "O singură zi",
- "button.view-details": "Vizualizare detalii",
"label.accounts": "Conturi",
+ "label.add-account": "Adăugare cont",
+ "label.add-website": "Adăugare site web",
"label.administrator": "Administrator",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Înapoi",
+ "label.cancel": "Anulează",
+ "label.change-password": "Schimbare parolă",
"label.confirm-password": "Confirmare parolă",
+ "label.copy-to-clipboard": "Copiază în clipboard",
"label.current-password": "Parola curentă",
"label.custom-range": "Interval personalizat",
"label.dashboard": "Tablou de bord",
+ "label.date-range": "Interval de date",
"label.default-date-range": "Interval de date implicit",
+ "label.delete": "Șterge",
+ "label.delete-account": "Ștergere cont",
+ "label.delete-website": "Ștergere site web",
+ "label.dismiss": "Renunță",
"label.domain": "Domeniu",
+ "label.edit": "Editare",
+ "label.edit-account": "Editare cont",
+ "label.edit-website": "Editare site web",
"label.enable-share-url": "Activare adresa URL de distribuire",
"label.invalid": "Invalid",
"label.invalid-domain": "Invalid domain",
"label.last-days": "Ultimele {x} zile",
"label.last-hours": "Ultimele {x} ore",
"label.logged-in-as": "Autentificat ca {username}",
+ "label.login": "Autentificare",
"label.logout": "Dezautentificare",
+ "label.more": "Mai mult",
"label.name": "Nume",
"label.new-password": "Parola nouă",
"label.password": "Parolă",
"label.passwords-dont-match": "Parolele nu se potrivesc",
"label.profile": "Profil",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Reîmprospătare",
"label.required": "Obligatoriu",
+ "label.reset": "Resetează",
+ "label.save": "Salvează",
"label.settings": "Setări",
+ "label.share-url": "Partajare URL",
+ "label.single-day": "O singură zi",
"label.this-month": "Această lună",
"label.this-week": "Această săptămână",
"label.this-year": "Acest an",
"label.timezone": "Fus orar",
"label.today": "Astăzi",
+ "label.tracking-code": "Cod de urmărire",
"label.unknown": "Necunoscut",
"label.username": "Username",
+ "label.view-details": "Vizualizare detalii",
"label.websites": "Site-uri web",
"message.active-users": "{x} {x, plural, one {vizitator activ} other {vizitatori activi}}",
"message.confirm-delete": "Sunteți sigur că doriți să ștergeți {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Obține codul de urmărire",
"message.go-to-settings": "Mergi la Setări",
"message.incorrect-username-password": "Username/parolă incorecte.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
"message.new-version-available": "Este disponibilă o nouă versiune {version} de umami!",
"message.no-data-available": "Nicio informație disponibilă.",
"message.no-websites-configured": "Nu aveți niciun site web configurat.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Site-uri de proveniență",
"metrics.unique-visitors": "Vizitatori unici",
"metrics.views": "Vizualizări",
- "metrics.visitors": "Vizitatori",
- "title.add-account": "Adăugare cont",
- "title.add-website": "Adăugare site web",
- "title.change-password": "Schimbare parolă",
- "title.delete-account": "Ștergere cont",
- "title.delete-website": "Ștergere site web",
- "title.edit-account": "Editare cont",
- "title.edit-website": "Editare site web",
- "title.share-url": "Partajare URL",
- "title.tracking-code": "Cod de urmărire"
+ "metrics.visitors": "Vizitatori"
}
diff --git a/lang/ru-RU.json b/lang/ru-RU.json
index 74985574..e9b7f43e 100644
--- a/lang/ru-RU.json
+++ b/lang/ru-RU.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Добавить аккаунт",
- "button.add-website": "Добавить сайт",
- "button.back": "Назад",
- "button.cancel": "Отменить",
- "button.change-password": "Изменить пароль",
- "button.copy-to-clipboard": "Скопировать в буфер обмена",
- "button.date-range": "Диапазон дат",
- "button.delete": "Удалить",
- "button.dismiss": "Отклонить",
- "button.edit": "Редактировать",
- "button.login": "Войти",
- "button.more": "Больше",
- "button.refresh": "Обновить",
- "button.reset": "Сбросить",
- "button.save": "Сохранить",
- "button.single-day": "Один день",
- "button.view-details": "Посмотреть детали",
"label.accounts": "Аккаунты",
+ "label.add-account": "Добавить аккаунт",
+ "label.add-website": "Добавить сайт",
"label.administrator": "Администратор",
+ "label.all": "Все",
+ "label.all-websites": "Все сайты",
+ "label.back": "Назад",
+ "label.cancel": "Отменить",
+ "label.change-password": "Изменить пароль",
"label.confirm-password": "Подтвердить пароль",
+ "label.copy-to-clipboard": "Скопировать в буфер обмена",
"label.current-password": "Текущий пароль",
"label.custom-range": "Другой период",
"label.dashboard": "Информационная панель",
+ "label.date-range": "Диапазон дат",
"label.default-date-range": "Диапазон дат по-умолчанию",
+ "label.delete": "Удалить",
+ "label.delete-account": "Удалить аккаунт",
+ "label.delete-website": "Удалить сайт",
+ "label.dismiss": "Отклонить",
"label.domain": "Домен",
+ "label.edit": "Редактировать",
+ "label.edit-account": "Редактировать аккаунт",
+ "label.edit-website": "Редактировать сайт",
"label.enable-share-url": "Разрешить делиться ссылкой",
"label.invalid": "Некорректный",
"label.invalid-domain": "Некорректный домен",
"label.last-days": "Последние {x} дней",
"label.last-hours": "Последние {x} часа",
"label.logged-in-as": "Вы вошли как {username}",
+ "label.login": "Войти",
"label.logout": "Выйти",
+ "label.more": "Больше",
"label.name": "Имя",
"label.new-password": "Новый пароль",
"label.password": "Пароль",
"label.passwords-dont-match": "Пароли не совпадают",
"label.profile": "Профиль",
+ "label.realtime": "Реальное время",
+ "label.realtime-logs": "Логи в реальном времени",
+ "label.refresh": "Обновить",
"label.required": "Обязательное",
+ "label.reset": "Сбросить",
+ "label.save": "Сохранить",
"label.settings": "Настройки",
+ "label.share-url": "Поделиться ссылкой",
+ "label.single-day": "Один день",
"label.this-month": "Этот месяц",
"label.this-week": "Эта неделя",
"label.this-year": "Этот год",
"label.timezone": "Часовой пояс",
"label.today": "Сегодня",
+ "label.tracking-code": "Код отслеживания",
"label.unknown": "Неизвестно",
"label.username": "Имя пользователя",
+ "label.view-details": "Посмотреть детали",
"label.websites": "Сайты",
"message.active-users": "{x} текущих посетителей",
"message.confirm-delete": "Вы уверены, что хотите удалить {target}?",
@@ -55,6 +65,7 @@
"message.get-tracking-code": "Получить код отслеживания",
"message.go-to-settings": "Перейти к настройкам",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.",
+ "message.log.visitor": "Посетитель из {country} используя {browser} на {os} {device}",
"message.new-version-available": "Доступна новая версия umami {version}",
"message.no-data-available": "Нет данных.",
"message.no-websites-configured": "У вас нет настроенных сайтов.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Источники",
"metrics.unique-visitors": "Уникальные посетители",
"metrics.views": "Просмотры",
- "metrics.visitors": "Посетители",
- "title.add-account": "Добавить аккаунт",
- "title.add-website": "Добавить сайт",
- "title.change-password": "Изменить пароль",
- "title.delete-account": "Удалить аккаунт",
- "title.delete-website": "Удалить сайт",
- "title.edit-account": "Редактировать аккаунт",
- "title.edit-website": "Редактировать сайт",
- "title.share-url": "Поделиться ссылкой",
- "title.tracking-code": "Код отслеживания"
+ "metrics.visitors": "Посетители"
}
diff --git a/lang/sv-SE.json b/lang/sv-SE.json
index 9e5e5958..3023ed90 100644
--- a/lang/sv-SE.json
+++ b/lang/sv-SE.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Lägg till konto",
- "button.add-website": "Lägg till webbsajt",
- "button.back": "Tillbaka",
- "button.cancel": "Avbryt",
- "button.change-password": "Byt lösenord",
- "button.copy-to-clipboard": "Kopiera till urklipp",
- "button.date-range": "Datumomfång",
- "button.delete": "Radera",
- "button.dismiss": "Dismiss",
- "button.edit": "Redigera",
- "button.login": "Logga in",
- "button.more": "Mer",
- "button.refresh": "Uppdatera",
- "button.reset": "Återställ",
- "button.save": "Spara",
- "button.single-day": "En dag",
- "button.view-details": "Visa detaljer",
"label.accounts": "Konton",
+ "label.add-account": "Lägg till konto",
+ "label.add-website": "Lägg till webbsajt",
"label.administrator": "Administratör",
+ "label.all": "Alla",
+ "label.all-websites": "Alla sajter",
+ "label.back": "Tillbaka",
+ "label.cancel": "Avbryt",
+ "label.change-password": "Byt lösenord",
"label.confirm-password": "Bekräfta lösenord",
+ "label.copy-to-clipboard": "Kopiera till urklipp",
"label.current-password": "Nuvarande lösenord",
"label.custom-range": "Anpassat urval",
- "label.dashboard": "Instrumentpanel",
+ "label.dashboard": "Översikt",
+ "label.date-range": "Datumomfång",
"label.default-date-range": "Standard datum-urval",
+ "label.delete": "Radera",
+ "label.delete-account": "Radera konto",
+ "label.delete-website": "Radera webbsajt",
+ "label.dismiss": "Avbryt",
"label.domain": "Domän",
+ "label.edit": "Redigera",
+ "label.edit-account": "Redigera konto",
+ "label.edit-website": "Redigera webbsajt",
"label.enable-share-url": "Aktivera delnings-URL",
"label.invalid": "Ogiltig",
"label.invalid-domain": "Ogiltig domän",
"label.last-days": "Senaste {x} dagarna",
"label.last-hours": "Senaste {x} timmarna",
"label.logged-in-as": "Inloggad som {username}",
+ "label.login": "Logga in",
"label.logout": "Logga ut",
+ "label.more": "Mer",
"label.name": "Namn",
"label.new-password": "Nytt lösenord",
"label.password": "Lösenord",
"label.passwords-dont-match": "Lösenorden är inte samma",
"label.profile": "Profil",
+ "label.realtime": "Realtid",
+ "label.realtime-logs": "Realtidsloggar",
+ "label.refresh": "Uppdatera",
"label.required": "Krävs",
+ "label.reset": "Återställ",
+ "label.save": "Spara",
"label.settings": "Inställningar",
+ "label.share-url": "Delnings-URL",
+ "label.single-day": "En dag",
"label.this-month": "Denna månad",
"label.this-week": "Denna vecka",
"label.this-year": "Detta år",
"label.timezone": "Tidszon",
"label.today": "Idag",
+ "label.tracking-code": "Spårningskod",
"label.unknown": "Okänd",
"label.username": "Användarnamn",
+ "label.view-details": "Visa detaljer",
"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}?",
@@ -55,7 +65,8 @@
"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.new-version-available": "A new version of umami {version} is available!",
+ "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.",
@@ -69,8 +80,8 @@
"metrics.bounce-rate": "Avvisningfrekvens",
"metrics.browsers": "Webbläsare",
"metrics.countries": "Länder",
- "metrics.device.desktop": "Desktop",
- "metrics.device.laptop": "Laptop",
+ "metrics.device.desktop": "Stationär",
+ "metrics.device.laptop": "Bärbar",
"metrics.device.mobile": "Mobil",
"metrics.device.tablet": "Platta",
"metrics.devices": "Enheter",
@@ -84,14 +95,5 @@
"metrics.referrers": "Hänvisare",
"metrics.unique-visitors": "Unika besökare",
"metrics.views": "Visningar",
- "metrics.visitors": "Besökare",
- "title.add-account": "Lägg till konto",
- "title.add-website": "Lägg till webbsajt",
- "title.change-password": "Byt lösenord",
- "title.delete-account": "Radera konto",
- "title.delete-website": "Radera webbsajt",
- "title.edit-account": "Redigera konto",
- "title.edit-website": "Redigera webbsajt",
- "title.share-url": "Delnings-URL",
- "title.tracking-code": "Spårningskod"
+ "metrics.visitors": "Besökare"
}
diff --git a/lang/tr-TR.json b/lang/tr-TR.json
index 2e84051d..78d64bb7 100644
--- a/lang/tr-TR.json
+++ b/lang/tr-TR.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "Yeni Hesap Ekle",
- "button.add-website": "Web sitesi ekle",
- "button.back": "Geri",
- "button.cancel": "İptal",
- "button.change-password": "Şifre değiştir",
- "button.copy-to-clipboard": "Panoya kopyala",
- "button.date-range": "Tarih aralığı",
- "button.delete": "Sil",
- "button.dismiss": "Dismiss",
- "button.edit": "Düzenle",
- "button.login": "Giriş Yap",
- "button.more": "Detaylı göster",
- "button.refresh": "Yenile",
- "button.reset": "Sıfırla",
- "button.save": "Kaydet",
- "button.single-day": "Tekil gün",
- "button.view-details": "Detayı incele",
"label.accounts": "Hesaplar",
+ "label.add-account": "Hesap ekle",
+ "label.add-website": "Web sitesi ekle",
"label.administrator": "Yönetici",
+ "label.all": "Tümü",
+ "label.all-websites": "Tüm web siteleri",
+ "label.back": "Geri",
+ "label.cancel": "İptal",
+ "label.change-password": "Şifre değiştir",
"label.confirm-password": "Parolayı onayla",
+ "label.copy-to-clipboard": "Panoya kopyala",
"label.current-password": "Mevcut parola",
"label.custom-range": "Özelleştirilmiş aralık",
"label.dashboard": "Kontrol Paneli",
+ "label.date-range": "Tarih aralığı",
"label.default-date-range": "Varsayılan tarih aralığı",
+ "label.delete": "Sil",
+ "label.delete-account": "Hesabı sil",
+ "label.delete-website": "Web sitesini sil",
+ "label.dismiss": "Reddet",
"label.domain": "Alan adı",
+ "label.edit": "Düzenle",
+ "label.edit-account": "Hesabı düzenle",
+ "label.edit-website": "Web sitesini düzenle",
"label.enable-share-url": "Anonim paylaşım URL'i aktif",
- "label.invalid": "Geçeriz",
+ "label.invalid": "Geçersiz",
"label.invalid-domain": "Geçersiz alan adı",
"label.last-days": "Son {x} gün",
"label.last-hours": "Son {x} saat",
"label.logged-in-as": "{username} olarak giriş yapıldı.",
+ "label.login": "Giriş Yap",
"label.logout": "Çıkış Yap",
+ "label.more": "Detaylı göster",
"label.name": "İsim",
"label.new-password": "Yeni parola",
"label.password": "Parola",
"label.passwords-dont-match": "Parolalar uyuşmuyor",
"label.profile": "Profil",
+ "label.realtime": "Gerçek Zamanlı",
+ "label.realtime-logs": "Gerçek zamanlı kayıtlar",
+ "label.refresh": "Yenile",
"label.required": "Zorunlu alan",
+ "label.reset": "Sıfırla",
+ "label.save": "Kaydet",
"label.settings": "Ayarlar",
+ "label.share-url": "Paylaşım adresi",
+ "label.single-day": "Tekil gün",
"label.this-month": "Bu ay",
"label.this-week": "Bu hafta",
"label.this-year": "Bu yıl",
"label.timezone": "Zaman dilimi",
"label.today": "Bugün",
+ "label.tracking-code": "İzleme kodu",
"label.unknown": "Bilinmeyen",
"label.username": "Kullanıcı adı",
+ "label.view-details": "Detayı incele",
"label.websites": "Web siteleri",
"message.active-users": "{x} aktif ziyaretçi",
"message.confirm-delete": "{target} kaydını silmek istediğinizden emin misiniz?",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "İzleme kodunu al",
"message.go-to-settings": "Ayarlara git",
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "Yeni ziyaretçi: {country}, {os}, {device}, {browser}",
+ "message.new-version-available": "umami'nin yeni bir versiyonu ({version}) mevcut!",
"message.no-data-available": "Henüz hiç veri yok.",
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
"message.page-not-found": "Sayfa bulunamadı.",
@@ -84,14 +95,5 @@
"metrics.referrers": "Yönlendirenler",
"metrics.unique-visitors": "Tekil kullanıcı",
"metrics.views": "Görüntüleme",
- "metrics.visitors": "Ziyaretçi",
- "title.add-account": "Hesap ekle",
- "title.add-website": "Web sitesi ekle",
- "title.change-password": "Şifre değiştir",
- "title.delete-account": "Hesabı sil",
- "title.delete-website": "Web sitesini sil",
- "title.edit-account": "Hesabı düzenle",
- "title.edit-website": "Web sitesini düzenle",
- "title.share-url": "Paylaşım adresi",
- "title.tracking-code": "İzleme kodu"
+ "metrics.visitors": "Ziyaretçi"
}
diff --git a/lang/uk-UA.json b/lang/uk-UA.json
new file mode 100644
index 00000000..f1631d30
--- /dev/null
+++ b/lang/uk-UA.json
@@ -0,0 +1,99 @@
+{
+ "label.accounts": "Облікові записи",
+ "label.add-account": "Додати обліковий запис",
+ "label.add-website": "Додати website",
+ "label.administrator": "Адміністратор",
+ "label.all": "All",
+ "label.all-websites": "All websites",
+ "label.back": "Назад",
+ "label.cancel": "Відмінити",
+ "label.change-password": "Змінити пароль",
+ "label.confirm-password": "Підтвердити пароль",
+ "label.copy-to-clipboard": "Копіювати до буферу обміну",
+ "label.current-password": "Поточний пароль",
+ "label.custom-range": "Довільний період",
+ "label.dashboard": "Інформаційна панель",
+ "label.date-range": "Діапазон дат",
+ "label.default-date-range": "Діапазон дат за умовчанням",
+ "label.delete": "Видалити",
+ "label.delete-account": "Видалити обліковий запис",
+ "label.delete-website": "Видалити веб-сайт",
+ "label.dismiss": "Відхилити",
+ "label.domain": "Домен",
+ "label.edit": "Редагувати",
+ "label.edit-account": "Редагувати обліковий запис",
+ "label.edit-website": "Редагувати веб-сайт",
+ "label.enable-share-url": "Дозволити ділитися посиланням",
+ "label.invalid": "Некоректний",
+ "label.invalid-domain": "Некоректний домен",
+ "label.last-days": "Останні {x} днів",
+ "label.last-hours": "Останні {x} годин",
+ "label.logged-in-as": "Ви увійшли як {username}",
+ "label.login": "Увійти",
+ "label.logout": "Вийти",
+ "label.more": "Більше",
+ "label.name": "Ім'я",
+ "label.new-password": "Новий пароль",
+ "label.password": "Пароль",
+ "label.passwords-dont-match": "Паролі не співпадають",
+ "label.profile": "Профіль",
+ "label.realtime": "Realtime",
+ "label.realtime-logs": "Realtime logs",
+ "label.refresh": "Оновити",
+ "label.required": "Обов'язкове",
+ "label.reset": "Скинути",
+ "label.save": "Зберегти",
+ "label.settings": "Налаштування",
+ "label.share-url": "Поділитися посилання",
+ "label.single-day": "Один день",
+ "label.this-month": "Поточний місяць",
+ "label.this-week": "Поточний тиждень",
+ "label.this-year": "Поточний рік",
+ "label.timezone": "Часовий пояс",
+ "label.today": "Сьогодні",
+ "label.tracking-code": "Код для відслідковування",
+ "label.unknown": "Невідомо",
+ "label.username": "Ім'я користувача",
+ "label.view-details": "Переглянути деталі",
+ "label.websites": "Веб-сайти",
+ "message.active-users": "{x} поточних відвідувачів",
+ "message.confirm-delete": "Ви впевнені, що бажаєте видалити {target}?",
+ "message.copied": "Скопійовано!",
+ "message.delete-warning": "Усі пов'язані дані будуть видалені також.",
+ "message.failure": "Щось пішло не так.",
+ "message.get-share-url": "Отримати публічне посилання",
+ "message.get-tracking-code": "Отримати код для відслідковування",
+ "message.go-to-settings": "Перейти до налаштувань",
+ "message.incorrect-username-password": "Невірне ім'я користувача або пароль.",
+ "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}",
+ "message.new-version-available": "Нова версія umami {version} доступна!",
+ "message.no-data-available": "Немає даних.",
+ "message.no-websites-configured": "У вас немає налаштованих веб-сайтів.",
+ "message.page-not-found": "Сторінку не знайдено.",
+ "message.powered-by": "На базі {name}",
+ "message.save-success": "Збережено успішно.",
+ "message.share-url": "Це публічне посилання для {target}.",
+ "message.track-stats": "Або відслідковувати статистику для {target}, розмістіть наступний код у {head} секції вашого веб-сайту.",
+ "message.type-delete": "Введіть {delete} у полі нижче щоб підтвердити.",
+ "metrics.actions": "Дії",
+ "metrics.average-visit-time": "Середній час візиту",
+ "metrics.bounce-rate": "Показник відмов",
+ "metrics.browsers": "Браузери",
+ "metrics.countries": "Країни",
+ "metrics.device.desktop": "Настільний комп'ютер",
+ "metrics.device.laptop": "Ноутбук",
+ "metrics.device.mobile": "Мобільний",
+ "metrics.device.tablet": "Планшет",
+ "metrics.devices": "Пристрої",
+ "metrics.events": "Події",
+ "metrics.filter.combined": "Об'єднані",
+ "metrics.filter.domain-only": "Лише домен",
+ "metrics.filter.raw": "Сирі дані",
+ "metrics.operating-systems": "Операційна система",
+ "metrics.page-views": "Перегляди сторінок",
+ "metrics.pages": "Сторінки",
+ "metrics.referrers": "Джерела",
+ "metrics.unique-visitors": "Унікальні відвідувачі",
+ "metrics.views": "Перегляди",
+ "metrics.visitors": "Відвідувачі"
+}
diff --git a/lang/zh-CN.json b/lang/zh-CN.json
index d5bf54cd..101cd6cd 100644
--- a/lang/zh-CN.json
+++ b/lang/zh-CN.json
@@ -1,50 +1,60 @@
{
- "button.add-account": "添加账户",
- "button.add-website": "添加网站",
- "button.back": "返回",
- "button.cancel": "取消",
- "button.change-password": "更新密码",
- "button.copy-to-clipboard": "复制",
- "button.date-range": "多日",
- "button.delete": "删除",
- "button.dismiss": "Dismiss",
- "button.edit": "编辑",
- "button.login": "登录",
- "button.more": "更多",
- "button.refresh": "刷新",
- "button.reset": "重置",
- "button.save": "保存",
- "button.single-day": "单日",
- "button.view-details": "查看更多",
"label.accounts": "账户",
+ "label.add-account": "添加账户",
+ "label.add-website": "添加网站",
"label.administrator": "管理员",
+ "label.all": "所有",
+ "label.all-websites": "全部网站",
+ "label.back": "返回",
+ "label.cancel": "取消",
+ "label.change-password": "更新密码",
"label.confirm-password": "确认密码",
+ "label.copy-to-clipboard": "复制",
"label.current-password": "目前密码",
"label.custom-range": "自定义时间段",
"label.dashboard": "仪表板",
+ "label.date-range": "多日",
"label.default-date-range": "默认日期范围",
+ "label.delete": "删除",
+ "label.delete-account": "删除账户",
+ "label.delete-website": "删除网站",
+ "label.dismiss": "关闭",
"label.domain": "域名",
+ "label.edit": "编辑",
+ "label.edit-account": "编辑账户",
+ "label.edit-website": "编辑网站",
"label.enable-share-url": "激活共享链接",
"label.invalid": "输入无效",
"label.invalid-domain": "无效域名",
"label.last-days": "最近 {x} 天",
"label.last-hours": "最近 {x} 小时",
"label.logged-in-as": "登录名: {username}",
+ "label.login": "登录",
"label.logout": "退出",
+ "label.more": "更多",
"label.name": "名字",
"label.new-password": "新密码",
"label.password": "密码",
"label.passwords-dont-match": "密码不一致",
"label.profile": "个人资料",
+ "label.realtime": "实时",
+ "label.realtime-logs": "实时日志",
+ "label.refresh": "刷新",
"label.required": "必填",
+ "label.reset": "重置",
+ "label.save": "保存",
"label.settings": "设置",
+ "label.share-url": "共享链接",
+ "label.single-day": "单日",
"label.this-month": "本月",
"label.this-week": "本周",
"label.this-year": "今年",
"label.timezone": "时区",
"label.today": "今天",
+ "label.tracking-code": "跟踪代码",
"label.unknown": "未知",
"label.username": "用户名",
+ "label.view-details": "查看更多",
"label.websites": "网站",
"message.active-users": "当前在线 {x} 人",
"message.confirm-delete": "你确定要删除{target}吗?",
@@ -55,7 +65,8 @@
"message.get-tracking-code": "获得跟踪代码",
"message.go-to-settings": "去设置",
"message.incorrect-username-password": "用户名密码不正确.",
- "message.new-version-available": "A new version of umami {version} is available!",
+ "message.log.visitor": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 进行访问.",
+ "message.new-version-available": "umami 有新版本 {version} 发布啦!",
"message.no-data-available": "无可用数据.",
"message.no-websites-configured": "你还没有设置任何网站.",
"message.page-not-found": "网页未找到.",
@@ -84,14 +95,5 @@
"metrics.referrers": "指入域名",
"metrics.unique-visitors": "独立访客",
"metrics.views": "页面流量",
- "metrics.visitors": "独立访客",
- "title.add-account": "添加账户",
- "title.add-website": "添加网站",
- "title.change-password": "更新密码",
- "title.delete-account": "删除账户",
- "title.delete-website": "删除网站",
- "title.edit-account": "编辑账户",
- "title.edit-website": "编辑网站",
- "title.share-url": "共享链接",
- "title.tracking-code": "跟踪代码"
+ "metrics.visitors": "独立访客"
}
diff --git a/lib/auth.js b/lib/auth.js
index 370db1c4..acfbe422 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -1,6 +1,6 @@
import { parse } from 'cookie';
import { parseSecureToken, parseToken } from './crypto';
-import { AUTH_COOKIE_NAME } from './constants';
+import { AUTH_COOKIE_NAME, TOKEN_HEADER } from './constants';
import { getWebsiteById } from './queries';
export async function getAuthToken(req) {
@@ -26,13 +26,14 @@ export async function isValidToken(token, validation) {
}
export async function allowQuery(req, skipToken) {
- const { id, token } = req.query;
+ const { id } = req.query;
+ const token = req.headers[TOKEN_HEADER];
const websiteId = +id;
const website = await getWebsiteById(websiteId);
if (website) {
- if (token && !skipToken) {
+ if (token && token !== 'undefined' && !skipToken) {
return isValidToken(token, { website_id: websiteId });
}
diff --git a/lib/constants.js b/lib/constants.js
index 87e0cf1d..65702620 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -4,6 +4,16 @@ export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range';
export const THEME_CONFIG = 'umami.theme';
export const VERSION_CHECK = 'umami.version-check';
+export const TOKEN_HEADER = 'x-umami-token';
+
+export const DEFAULT_LOCALE = 'en-US';
+export const DEFAULT_THEME = 'light';
+export const DEFAUL_CHART_HEIGHT = 400;
+export const DEFAULT_ANIMATION_DURATION = 300;
+export const DEFAULT_DATE_RANGE = '24hour';
+
+export const REALTIME_RANGE = 30;
+export const REALTIME_INTERVAL = 3000;
export const THEME_COLORS = {
light: {
@@ -42,13 +52,15 @@ export const EVENT_COLORS = [
'#44b556',
'#e68619',
'#e34850',
- '#1b959a',
- '#d83790',
- '#85d044',
+ '#f7bd12',
+ '#01bad7',
+ '#6734bc',
+ '#89c541',
+ '#ffc301',
+ '#ec1562',
+ '#ffec16',
];
-export const DEFAULT_DATE_RANGE = '24hour';
-
export const POSTGRESQL = 'postgresql';
export const MYSQL = 'mysql';
@@ -68,10 +80,6 @@ export const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01',
};
-export const FILTER_DOMAIN_ONLY = 0;
-export const FILTER_COMBINED = 1;
-export const FILTER_RAW = 2;
-
export const DOMAIN_REGEX = /localhost(:\d{1,5})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}/;
export const DESKTOP_SCREEN_WIDTH = 1920;
diff --git a/lib/date.js b/lib/date.js
index cdfe322c..50d623bb 100644
--- a/lib/date.js
+++ b/lib/date.js
@@ -7,6 +7,7 @@ import {
addYears,
subHours,
subDays,
+ startOfMinute,
startOfHour,
startOfDay,
startOfWeek,
@@ -17,6 +18,7 @@ import {
endOfWeek,
endOfMonth,
endOfYear,
+ differenceInMinutes,
differenceInHours,
differenceInCalendarDays,
differenceInCalendarMonths,
@@ -114,6 +116,7 @@ export function getDateFromString(str) {
}
const dateFuncs = {
+ minute: [differenceInMinutes, addMinutes, startOfMinute],
hour: [differenceInHours, addHours, startOfHour],
day: [differenceInCalendarDays, addDays, startOfDay],
month: [differenceInCalendarMonths, addMonths, startOfMonth],
diff --git a/lib/format.js b/lib/format.js
index b031509b..a336c1c4 100644
--- a/lib/format.js
+++ b/lib/format.js
@@ -62,3 +62,19 @@ export function formatLongNumber(value) {
return formatNumber(n);
}
+
+export function stringToColor(str) {
+ if (!str) {
+ return '#ffffff';
+ }
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ let color = '#';
+ for (let i = 0; i < 3; i++) {
+ let value = (hash >> (i * 8)) & 0xff;
+ color += ('00' + value.toString(16)).substr(-2);
+ }
+ return color;
+}
diff --git a/lib/lang.js b/lib/lang.js
index 02dd5141..4b1841f5 100644
--- a/lib/lang.js
+++ b/lib/lang.js
@@ -1,5 +1,24 @@
import { format } from 'date-fns';
-import { enUS, nl, zhCN, tr, ru, de, ja, es, fr, da, sv, el, pt, ro } from 'date-fns/locale';
+import {
+ enUS,
+ nl,
+ zhCN,
+ tr,
+ ru,
+ de,
+ ja,
+ es,
+ fr,
+ da,
+ sv,
+ el,
+ pt,
+ ro,
+ nb,
+ id,
+ uk,
+ fi,
+} from 'date-fns/locale';
import enMessages from 'lang-compiled/en-US.json';
import nlMessages from 'lang-compiled/nl-NL.json';
import zhCNMessages from 'lang-compiled/zh-CN.json';
@@ -16,6 +35,10 @@ import grMessages from 'lang-compiled/el-GR.json';
import foMessages from 'lang-compiled/fo-FO.json';
import ptMessages from 'lang-compiled/pt-PT.json';
import roMessages from 'lang-compiled/ro-RO.json';
+import nbNOMessages from 'lang-compiled/nb-NO.json';
+import idMessages from 'lang-compiled/id-ID.json';
+import ukMessages from 'lang-compiled/uk-UA.json';
+import fiMessages from 'lang-compiled/fi-FI.json';
export const messages = {
'en-US': enMessages,
@@ -34,6 +57,10 @@ export const messages = {
'fo-FO': foMessages,
'pt-PT': ptMessages,
'ro-RO': roMessages,
+ 'nb-NO': nbNOMessages,
+ 'id-ID': idMessages,
+ 'uk-UA': ukMessages,
+ 'fi-FI': fiMessages,
};
export const dateLocales = {
@@ -53,25 +80,33 @@ export const dateLocales = {
'fo-FO': da,
'pt-PT': pt,
'ro-RO': ro,
+ 'nb-NO': nb,
+ 'id-ID': id,
+ 'uk-UA': uk,
+ 'fi-FI': fi,
};
export const menuOptions = [
- { label: 'English', value: 'en-US', display: 'en' },
{ label: '中文', value: 'zh-CN', display: 'cn' },
{ label: 'Dansk', value: 'da-DK', display: 'da' },
{ label: 'Deutsch', value: 'de-DE', display: 'de' },
+ { label: 'English', value: 'en-US', display: 'en' },
{ label: 'Español', value: 'es-MX', display: 'es' },
{ label: 'Føroyskt', value: 'fo-FO', display: 'fo' },
{ label: 'Français', value: 'fr-FR', display: 'fr' },
{ label: 'Ελληνικά', value: 'el-GR', display: 'el' },
+ { label: 'Bahasa Indonesia', value: 'id-ID', display: 'id' },
{ label: '日本語', value: 'ja-JP', display: 'ja' },
{ label: 'Монгол', value: 'mn-MN', display: 'mn' },
{ label: 'Nederlands', value: 'nl-NL', display: 'nl' },
+ { label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' },
{ label: 'Português', value: 'pt-PT', display: 'pt' },
{ label: 'Русский', value: 'ru-RU', display: 'ru' },
{ label: 'Română', value: 'ro-RO', display: 'ro' },
+ { label: 'Suomi', value: 'fi-FI', display: 'fi' },
{ label: 'Svenska', value: 'sv-SE', display: 'sv' },
{ label: 'Türkçe', value: 'tr-TR', display: 'tr' },
+ { label: 'українська', value: 'uk-UA', display: 'uk' },
];
export function dateFormat(date, str, locale) {
diff --git a/lib/queries.js b/lib/queries.js
index d10777fd..ed97eb93 100644
--- a/lib/queries.js
+++ b/lib/queries.js
@@ -16,16 +16,12 @@ export function getDatabase() {
}
export async function runQuery(query) {
- return query
- .catch(e => {
- throw e;
- })
- .finally(async () => {
- await prisma.$disconnect();
- });
+ return query.catch(e => {
+ throw e;
+ });
}
-export async function rawQuery(query, params) {
+export async function rawQuery(query, params = []) {
const db = getDatabase();
if (db !== POSTGRESQL && db !== MYSQL) {
@@ -170,16 +166,6 @@ export async function createSession(website_id, data) {
);
}
-export async function getSessionById(session_id) {
- return runQuery(
- prisma.session.findOne({
- where: {
- session_id,
- },
- }),
- );
-}
-
export async function getSessionByUuid(session_uuid) {
return runQuery(
prisma.session.findOne({
@@ -289,7 +275,58 @@ export async function createAccount(data) {
);
}
-export function getMetrics(website_id, start_at, end_at, filters = {}) {
+export async function getSessions(websites, start_at) {
+ return runQuery(
+ prisma.session.findMany({
+ where: {
+ website: {
+ website_id: {
+ in: websites,
+ },
+ },
+ created_at: {
+ gte: start_at,
+ },
+ },
+ }),
+ );
+}
+
+export async function getPageviews(websites, start_at) {
+ return runQuery(
+ prisma.pageview.findMany({
+ where: {
+ website: {
+ website_id: {
+ in: websites,
+ },
+ },
+ created_at: {
+ gte: start_at,
+ },
+ },
+ }),
+ );
+}
+
+export async function getEvents(websites, start_at) {
+ return runQuery(
+ prisma.event.findMany({
+ where: {
+ website: {
+ website_id: {
+ in: websites,
+ },
+ },
+ created_at: {
+ gte: start_at,
+ },
+ },
+ }),
+ );
+}
+
+export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = '';
@@ -321,7 +358,7 @@ export function getMetrics(website_id, start_at, end_at, filters = {}) {
);
}
-export function getPageviews(
+export function getPageviewStats(
website_id,
start_at,
end_at,
@@ -429,7 +466,7 @@ export function getActiveVisitors(website_id) {
);
}
-export function getEvents(
+export function getEventMetrics(
website_id,
start_at,
end_at,
@@ -463,3 +500,30 @@ export function getEvents(
params,
);
}
+
+export async function getRealtimeData(websites, time) {
+ const [pageviews, sessions, events] = await Promise.all([
+ getPageviews(websites, time),
+ getSessions(websites, time),
+ getEvents(websites, time),
+ ]);
+
+ return {
+ pageviews: pageviews.map(({ view_id, ...props }) => ({
+ __id: `p${view_id}`,
+ view_id,
+ ...props,
+ })),
+ sessions: sessions.map(({ session_id, ...props }) => ({
+ __id: `s${session_id}`,
+ session_id,
+ ...props,
+ })),
+ events: events.map(({ event_id, ...props }) => ({
+ __id: `e${event_id}`,
+ event_id,
+ ...props,
+ })),
+ timestamp: Date.now(),
+ };
+}
diff --git a/lib/session.js b/lib/session.js
index 0488b730..3d2fc7bc 100644
--- a/lib/session.js
+++ b/lib/session.js
@@ -1,6 +1,6 @@
import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries';
import { getClientInfo } from 'lib/request';
-import { uuid, isValidUuid } from 'lib/crypto';
+import { uuid, isValidUuid, parseToken } from 'lib/crypto';
export async function getSession(req) {
const { payload } = req.body;
@@ -9,7 +9,15 @@ export async function getSession(req) {
throw new Error('Invalid request');
}
- const { website: website_uuid, hostname, screen, language } = payload;
+ const { website: website_uuid, hostname, screen, language, cache } = payload;
+
+ if (cache) {
+ const result = await parseToken(cache);
+
+ if (result) {
+ return result;
+ }
+ }
if (!isValidUuid(website_uuid)) {
throw new Error(`Invalid website: ${website_uuid}`);
diff --git a/lib/url.js b/lib/url.js
index 500736f9..deec7fe1 100644
--- a/lib/url.js
+++ b/lib/url.js
@@ -28,3 +28,7 @@ export function getQueryString(params = {}) {
return '';
}
+
+export function makeUrl(url, params) {
+ return `${url}${getQueryString(params)}`;
+}
diff --git a/lib/web.js b/lib/web.js
index aceb6498..13889cbe 100644
--- a/lib/web.js
+++ b/lib/web.js
@@ -1,4 +1,4 @@
-import { getQueryString } from './url';
+import { makeUrl } from './url';
export const apiRequest = (method, url, body, headers) =>
fetch(url, {
@@ -19,13 +19,17 @@ export const apiRequest = (method, url, body, headers) =>
return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
});
-export const get = (url, params) => apiRequest('get', `${url}${getQueryString(params)}`);
+export const get = (url, params, headers) =>
+ apiRequest('get', makeUrl(url, params), undefined, headers);
-export const del = (url, params) => apiRequest('delete', `${url}${getQueryString(params)}`);
+export const del = (url, params, headers) =>
+ apiRequest('delete', makeUrl(url, params), undefined, headers);
-export const post = (url, params) => apiRequest('post', url, JSON.stringify(params));
+export const post = (url, params, headers) =>
+ apiRequest('post', url, JSON.stringify(params), headers);
-export const put = (url, params) => apiRequest('put', url, JSON.stringify(params));
+export const put = (url, params, headers) =>
+ apiRequest('put', url, JSON.stringify(params), headers);
export const hook = (_this, method, callback) => {
const orig = _this[method];
diff --git a/next.config.js b/next.config.js
index c1e31d7b..fc962a03 100644
--- a/next.config.js
+++ b/next.config.js
@@ -4,9 +4,7 @@ const pkg = require('./package.json');
module.exports = {
env: {
VERSION: pkg.version,
- },
- serverRuntimeConfig: {
- PROJECT_ROOT: __dirname,
+ FORCE_SSL: !!process.env.FORCE_SSL,
},
webpack(config) {
config.module.rules.push({
@@ -19,4 +17,17 @@ module.exports = {
return config;
},
+ async headers() {
+ return [
+ {
+ source: '/umami.js',
+ headers: [
+ {
+ key: 'Cache-Control',
+ value: 'public, max-age=2592000', // 30 days
+ },
+ ],
+ },
+ ];
+ },
};
diff --git a/package.json b/package.json
index 138ea282..ce9d5330 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.68.0",
+ "version": "0.96.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao ",
"license": "MIT",
@@ -31,7 +31,11 @@
"merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js",
"compile-lang": "formatjs compile-folder --ast build lang-compiled",
- "check-lang": "node scripts/check-lang.js"
+ "check-lang": "node scripts/check-lang.js",
+ "download-country-names": "node scripts/download-country-names.js",
+ "loadtest": "node scripts/loadtest.js",
+ "loadtest:medium": "node scripts/loadtest.js --weight=medium",
+ "loadtest:heavy": "node scripts/loadtest.js --weight=heavy --verbose"
},
"lint-staged": {
"**/*.js": [
@@ -52,7 +56,7 @@
}
},
"dependencies": {
- "@prisma/client": "2.8.0",
+ "@prisma/client": "2.9.0",
"@reduxjs/toolkit": "^1.4.0",
"bcrypt": "^5.0.0",
"chalk": "^4.1.0",
@@ -61,24 +65,25 @@
"cookie": "^0.4.1",
"cors": "^2.8.5",
"date-fns": "^2.16.1",
- "date-fns-tz": "^1.0.10",
- "detect-browser": "^5.1.1",
+ "date-fns-tz": "^1.0.12",
+ "detect-browser": "^5.2.0",
"dotenv": "^8.2.0",
- "formik": "^2.1.6",
+ "formik": "^2.2.0",
"immer": "^7.0.9",
"is-localhost-ip": "^1.4.0",
"isbot-fast": "^1.2.0",
"jose": "^2.0.2",
- "maxmind": "^4.2.0",
+ "maxmind": "^4.3.0",
"moment-timezone": "^0.5.31",
- "next": "^9.5.3",
+ "next": "^9.5.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
- "react-intl": "^5.8.3",
+ "react-intl": "^5.8.4",
"react-redux": "^7.2.1",
"react-simple-maps": "^2.1.2",
"react-spring": "^8.0.27",
"react-tooltip": "^4.2.10",
+ "react-use-measure": "^2.0.2",
"react-window": "^1.8.5",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
@@ -87,11 +92,11 @@
"thenby": "^1.3.4",
"timezone-support": "^2.0.2",
"tinycolor2": "^1.4.2",
- "uuid": "^8.3.0"
+ "uuid": "^8.3.1"
},
"devDependencies": {
- "@formatjs/cli": "^2.12.0",
- "@prisma/cli": "2.8.0",
+ "@formatjs/cli": "^2.13.2",
+ "@prisma/cli": "2.9.0",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.3",
@@ -99,21 +104,22 @@
"cross-env": "^7.0.2",
"del": "^6.0.0",
"dotenv-cli": "^4.0.0",
- "eslint": "^7.10.0",
+ "eslint": "^7.11.0",
"eslint-config-prettier": "^6.12.0",
"eslint-plugin-prettier": "^3.1.3",
- "eslint-plugin-react": "^7.21.2",
+ "eslint-plugin-react": "^7.21.4",
"eslint-plugin-react-hooks": "^4.1.2",
"extract-react-intl-messages": "^4.1.1",
"husky": "^4.3.0",
"lint-staged": "^10.4.0",
+ "loadtest": "5.1.0",
"npm-run-all": "^4.1.5",
"postcss-flexbugs-fixes": "^4.2.1",
"postcss-import": "^12.0.1",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.1.2",
"prettier-eslint": "^11.0.0",
- "rollup": "^2.28.2",
+ "rollup": "^2.30.0",
"rollup-plugin-hashbang": "^2.2.2",
"rollup-plugin-terser": "^7.0.2",
"stylelint": "^13.7.2",
diff --git a/pages/_app.js b/pages/_app.js
index 9aad4339..2849d2f0 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -3,6 +3,7 @@ import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { useStore } from 'redux/store';
import useLocale from 'hooks/useLocale';
+import useForceSSL from 'hooks/useForceSSL';
import { messages } from 'lib/lang';
import 'styles/variables.css';
import 'styles/bootstrap-grid.css';
@@ -21,6 +22,7 @@ const Intl = ({ children }) => {
};
export default function App({ Component, pageProps }) {
+ useForceSSL(process.env.FORCE_SSL);
const store = useStore();
return (
diff --git a/pages/api/collect.js b/pages/api/collect.js
index 399e88e9..a6140169 100644
--- a/pages/api/collect.js
+++ b/pages/api/collect.js
@@ -1,14 +1,26 @@
+import isBot from 'isbot-fast';
import { savePageView, saveEvent } from 'lib/queries';
import { useCors, useSession } from 'lib/middleware';
import { ok, badRequest } from 'lib/response';
-import isBot from 'isbot-fast';
+import { createToken } from 'lib/crypto';
+import { getIpAddress } from '../../lib/request';
export default async (req, res) => {
+ await useCors(req, res);
+
if (isBot(req.headers['user-agent'])) {
return ok(res);
}
- await useCors(req, res);
+ if (process.env.IGNORE_IP) {
+ const ips = process.env.IGNORE_IP.split(',').map(n => n.trim());
+ const ip = getIpAddress(req);
+
+ if (ips.includes(ip)) {
+ return ok(res);
+ }
+ }
+
await useSession(req, res);
const { type, payload } = req.body;
@@ -28,5 +40,7 @@ export default async (req, res) => {
return badRequest(res);
}
- return ok(res);
+ const token = await createToken({ website_id, session_id });
+
+ return ok(res, token);
};
diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js
new file mode 100644
index 00000000..52e66600
--- /dev/null
+++ b/pages/api/realtime/init.js
@@ -0,0 +1,26 @@
+import { subMinutes } from 'date-fns';
+import { useAuth } from 'lib/middleware';
+import { ok, methodNotAllowed } from 'lib/response';
+import { getUserWebsites, getRealtimeData } from 'lib/queries';
+import { createToken } from 'lib/crypto';
+
+export default async (req, res) => {
+ await useAuth(req, res);
+
+ if (req.method === 'GET') {
+ const { user_id } = req.auth;
+
+ const websites = await getUserWebsites(user_id);
+ const ids = websites.map(({ website_id }) => website_id);
+ const token = await createToken({ websites: ids });
+ const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
+
+ return ok(res, {
+ websites,
+ token,
+ data,
+ });
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/api/realtime/update.js b/pages/api/realtime/update.js
new file mode 100644
index 00000000..730219ae
--- /dev/null
+++ b/pages/api/realtime/update.js
@@ -0,0 +1,27 @@
+import { useAuth } from 'lib/middleware';
+import { ok, methodNotAllowed, badRequest } from 'lib/response';
+import { getRealtimeData } from 'lib/queries';
+import { parseToken } from 'lib/crypto';
+import { TOKEN_HEADER } from 'lib/constants';
+
+export default async (req, res) => {
+ await useAuth(req, res);
+
+ if (req.method === 'GET') {
+ const { start_at } = req.query;
+
+ const token = req.headers[TOKEN_HEADER];
+
+ if (!token) {
+ return badRequest(res);
+ }
+
+ const { websites } = await parseToken(token);
+
+ const data = await getRealtimeData(websites, new Date(+start_at));
+
+ return ok(res, data);
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js
index 0498052f..7d98717a 100644
--- a/pages/api/website/[id]/events.js
+++ b/pages/api/website/[id]/events.js
@@ -1,5 +1,5 @@
import moment from 'moment-timezone';
-import { getEvents } from 'lib/queries';
+import { getEventMetrics } from 'lib/queries';
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
import { allowQuery } from 'lib/auth';
@@ -21,7 +21,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- const events = await getEvents(websiteId, startDate, endDate, tz, unit, { url });
+ const events = await getEventMetrics(websiteId, startDate, endDate, tz, unit, { url });
return ok(res, events);
}
diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js
index 91a0f4cf..2aca489f 100644
--- a/pages/api/website/[id]/metrics.js
+++ b/pages/api/website/[id]/metrics.js
@@ -1,27 +1,67 @@
-import { getMetrics } from 'lib/queries';
-import { methodNotAllowed, ok, unauthorized } from 'lib/response';
+import { getPageviewMetrics, getSessionMetrics } from 'lib/queries';
+import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
+import { DOMAIN_REGEX } from 'lib/constants';
import { allowQuery } from 'lib/auth';
+const sessionColumns = ['browser', 'os', 'device', 'country'];
+const pageviewColumns = ['url', 'referrer'];
+
+function getTable(type) {
+ if (type === 'event') {
+ return 'event';
+ }
+
+ if (sessionColumns.includes(type)) {
+ return 'session';
+ }
+
+ return 'pageview';
+}
+
+function getColumn(type) {
+ if (type === 'event') {
+ return `concat(event_type, ':', event_value)`;
+ }
+ return type;
+}
+
export default async (req, res) => {
if (req.method === 'GET') {
if (!(await allowQuery(req))) {
return unauthorized(res);
}
- const { id, start_at, end_at, url } = req.query;
+ const { id, type, start_at, end_at, domain, url } = req.query;
+
+ if (domain && !DOMAIN_REGEX.test(domain)) {
+ return badRequest(res);
+ }
const websiteId = +id;
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- const metrics = await getMetrics(websiteId, startDate, endDate, { url });
+ if (sessionColumns.includes(type)) {
+ const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
- const stats = Object.keys(metrics[0]).reduce((obj, key) => {
- obj[key] = Number(metrics[0][key]) || 0;
- return obj;
- }, {});
+ return ok(res, data);
+ }
- return ok(res, stats);
+ if (type === 'event' || pageviewColumns.includes(type)) {
+ const data = await getPageviewMetrics(
+ websiteId,
+ startDate,
+ endDate,
+ getColumn(type),
+ getTable(type),
+ {
+ domain: type !== 'event' && domain,
+ url: type !== 'url' && url,
+ },
+ );
+
+ return ok(res, data);
+ }
}
return methodNotAllowed(res);
diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js
index 2191a4c4..965f28ae 100644
--- a/pages/api/website/[id]/pageviews.js
+++ b/pages/api/website/[id]/pageviews.js
@@ -1,5 +1,5 @@
import moment from 'moment-timezone';
-import { getPageviews } from 'lib/queries';
+import { getPageviewStats } from 'lib/queries';
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
import { allowQuery } from 'lib/auth';
@@ -21,12 +21,12 @@ export default async (req, res) => {
return badRequest(res);
}
- const [pageviews, uniques] = await Promise.all([
- getPageviews(websiteId, startDate, endDate, tz, unit, '*', url),
- getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url),
+ const [pageviews, sessions] = await Promise.all([
+ getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', url),
+ getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url),
]);
- return ok(res, { pageviews, uniques });
+ return ok(res, { pageviews, sessions });
}
return methodNotAllowed(res);
diff --git a/pages/api/website/[id]/rankings.js b/pages/api/website/[id]/rankings.js
deleted file mode 100644
index 17aa6daa..00000000
--- a/pages/api/website/[id]/rankings.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { getPageviewMetrics, getSessionMetrics } from 'lib/queries';
-import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
-import { DOMAIN_REGEX } from 'lib/constants';
-import { allowQuery } from 'lib/auth';
-
-const sessionColumns = ['browser', 'os', 'device', 'country'];
-const pageviewColumns = ['url', 'referrer'];
-
-function getTable(type) {
- if (type === 'event') {
- return 'event';
- }
-
- if (sessionColumns.includes(type)) {
- return 'session';
- }
-
- return 'pageview';
-}
-
-function getColumn(type) {
- if (type === 'event') {
- return `concat(event_type, ':', event_value)`;
- }
- return type;
-}
-
-export default async (req, res) => {
- if (req.method === 'GET') {
- if (!(await allowQuery(req))) {
- return unauthorized(res);
- }
-
- const { id, type, start_at, end_at, domain, url } = req.query;
-
- if (domain && !DOMAIN_REGEX.test(domain)) {
- return badRequest(res);
- }
-
- const websiteId = +id;
- const startDate = new Date(+start_at);
- const endDate = new Date(+end_at);
-
- if (sessionColumns.includes(type)) {
- const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
-
- return ok(res, data);
- }
-
- if (type === 'event' || pageviewColumns.includes(type)) {
- const data = await getPageviewMetrics(
- websiteId,
- startDate,
- endDate,
- getColumn(type),
- getTable(type),
- {
- domain,
- url: type !== 'url' && url,
- },
- );
-
- return ok(res, data);
- }
- }
-
- return methodNotAllowed(res);
-};
diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js
new file mode 100644
index 00000000..8b80a363
--- /dev/null
+++ b/pages/api/website/[id]/stats.js
@@ -0,0 +1,28 @@
+import { getWebsiteStats } from 'lib/queries';
+import { methodNotAllowed, ok, unauthorized } from 'lib/response';
+import { allowQuery } from 'lib/auth';
+
+export default async (req, res) => {
+ if (req.method === 'GET') {
+ if (!(await allowQuery(req))) {
+ return unauthorized(res);
+ }
+
+ const { id, start_at, end_at, url } = req.query;
+
+ const websiteId = +id;
+ const startDate = new Date(+start_at);
+ const endDate = new Date(+end_at);
+
+ const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url });
+
+ const stats = Object.keys(metrics[0]).reduce((obj, key) => {
+ obj[key] = Number(metrics[0][key]) || 0;
+ return obj;
+ }, {});
+
+ return ok(res, stats);
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/realtime.js b/pages/realtime.js
new file mode 100644
index 00000000..9f1ebffa
--- /dev/null
+++ b/pages/realtime.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import Layout from 'components/layout/Layout';
+import RealtimeDashboard from 'components/pages/RealtimeDashboard';
+import useRequireLogin from 'hooks/useRequireLogin';
+
+export default function RealtimePage() {
+ const { loading } = useRequireLogin();
+
+ if (loading) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/pages/share/[...id].js b/pages/share/[...id].js
index 85455af4..ff5e4a6e 100644
--- a/pages/share/[...id].js
+++ b/pages/share/[...id].js
@@ -2,23 +2,23 @@ import React from 'react';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/pages/WebsiteDetails';
-import useFetch from 'hooks/useFetch';
+import useShareToken from 'hooks/useShareToken';
export default function SharePage() {
const router = useRouter();
const { id } = router.query;
const shareId = id?.[0];
- const { data } = useFetch(shareId ? `/api/share/${shareId}` : null);
+ const shareToken = useShareToken(shareId);
- if (!data) {
+ if (!shareToken) {
return null;
}
- const { websiteId, token } = data;
+ const { websiteId } = shareToken;
return (
-
+
);
}
diff --git a/public/country/fi-FI.json b/public/country/fi-FI.json
new file mode 100644
index 00000000..6410ee12
--- /dev/null
+++ b/public/country/fi-FI.json
@@ -0,0 +1 @@
+{"AF":"Afganistan","AX":"Ahvenanmaa","NL":"Alankomaat","AL":"Albania","DZ":"Algeria","AS":"Amerikan Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua ja Barbuda","AE":"Arabiemiirikunnat","AR":"Argentiina","AM":"Armenia","AW":"Aruba","AU":"Australia","AZ":"Azerbaid\u017ean","BS":"Bahama","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia ja Hertsegovina","BW":"Botswana","BV":"Bouvet\u2019nsaari","BR":"Brasilia","IO":"Brittil\u00e4inen Intian valtameren alue","VG":"Brittil\u00e4iset Neitsytsaaret","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","KY":"Caymansaaret","CL":"Chile","CK":"Cookinsaaret","CR":"Costa Rica","CW":"Cura\u00e7ao","DJ":"Djibouti","DM":"Dominica","DO":"Dominikaaninen tasavalta","EC":"Ecuador","EG":"Egypti","SV":"El Salvador","ER":"Eritrea","ES":"Espanja","ZA":"Etel\u00e4-Afrikka","GS":"Etel\u00e4-Georgia ja Etel\u00e4iset Sandwichsaaret","KR":"Etel\u00e4-Korea","SS":"Etel\u00e4-Sudan","ET":"Etiopia","FK":"Falklandinsaaret","FJ":"Fid\u017ei","PH":"Filippiinit","FO":"F\u00e4rsaaret","GA":"Gabon","GM":"Gambia","GE":"Georgia","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Gr\u00f6nlanti","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard ja McDonaldinsaaret","HN":"Honduras","HK":"Hongkong \u2013 Kiinan e.h.a.","SJ":"Huippuvuoret ja Jan Mayen","ID":"Indonesia","IN":"Intia","IQ":"Irak","IR":"Iran","IE":"Irlanti","IS":"Islanti","GB":"Iso-Britannia","IL":"Israel","IT":"Italia","TL":"It\u00e4-Timor","AT":"It\u00e4valta","JM":"Jamaika","JP":"Japani","YE":"Jemen","JE":"Jersey","JO":"Jordania","CX":"Joulusaari","KH":"Kambod\u017ea","CM":"Kamerun","CA":"Kanada","CV":"Kap Verde","BQ":"Karibian Alankomaat","KZ":"Kazakstan","KE":"Kenia","CF":"Keski-Afrikan tasavalta","CN":"Kiina","KG":"Kirgisia","KI":"Kiribati","CO":"Kolumbia","KM":"Komorit","CD":"Kongon demokraattinen tasavalta","CG":"Kongon tasavalta","CC":"Kookossaaret (Keelingsaaret)","GR":"Kreikka","HR":"Kroatia","CU":"Kuuba","KW":"Kuwait","CY":"Kypros","LA":"Laos","LV":"Latvia","LS":"Lesotho","LB":"Libanon","LR":"Liberia","LY":"Libya","LI":"Liechtenstein","LT":"Liettua","LU":"Luxemburg","EH":"L\u00e4nsi-Sahara","MO":"Macao \u2013 Kiinan e.h.a.","MG":"Madagaskar","MW":"Malawi","MV":"Malediivit","MY":"Malesia","ML":"Mali","MT":"Malta","IM":"Mansaari","MA":"Marokko","MH":"Marshallinsaaret","MQ":"Martinique","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Meksiko","FM":"Mikronesian liittovaltio","MD":"Moldova","MC":"Monaco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mosambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","NF":"Norfolkinsaari","NO":"Norja","CI":"Norsunluurannikko","OM":"Oman","PK":"Pakistan","PW":"Palau","PS":"Palestiinalaisalueet","PA":"Panama","PG":"Papua-Uusi-Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn","KP":"Pohjois-Korea","MK":"Pohjois-Makedonia","MP":"Pohjois-Mariaanit","PT":"Portugali","PR":"Puerto Rico","PL":"Puola","GQ":"P\u00e4iv\u00e4ntasaajan Guinea","QA":"Qatar","FR":"Ranska","TF":"Ranskan etel\u00e4iset alueet","GF":"Ranskan Guayana","PF":"Ranskan Polynesia","RE":"R\u00e9union","RO":"Romania","RW":"Ruanda","SE":"Ruotsi","SH":"Saint Helena","KN":"Saint Kitts ja Nevis","LC":"Saint Lucia","VC":"Saint Vincent ja Grenadiinit","BL":"Saint-Barth\u00e9lemy","MF":"Saint-Martin","PM":"Saint-Pierre ja Miquelon","DE":"Saksa","SB":"Salomonsaaret","ZM":"Sambia","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 ja Pr\u00edncipe","SA":"Saudi-Arabia","SN":"Senegal","RS":"Serbia","SC":"Seychellit","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","LK":"Sri Lanka","SD":"Sudan","FI":"Suomi","SR":"Suriname","CH":"Sveitsi","SZ":"Swazimaa","SY":"Syyria","TJ":"Tad\u017eikistan","TW":"Taiwan","TZ":"Tansania","DK":"Tanska","TH":"Thaimaa","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad ja Tobago","TD":"T\u0161ad","CZ":"T\u0161ekki","TN":"Tunisia","TR":"Turkki","TM":"Turkmenistan","TC":"Turks- ja Caicossaaret","TV":"Tuvalu","UG":"Uganda","UA":"Ukraina","HU":"Unkari","UY":"Uruguay","NC":"Uusi-Kaledonia","NZ":"Uusi-Seelanti","UZ":"Uzbekistan","BY":"Valko-Ven\u00e4j\u00e4","VU":"Vanuatu","VA":"Vatikaani","VE":"Venezuela","RU":"Ven\u00e4j\u00e4","VN":"Vietnam","EE":"Viro","WF":"Wallis ja Futuna","US":"Yhdysvallat","UM":"Yhdysvaltain erillissaaret","VI":"Yhdysvaltain Neitsytsaaret","ZW":"Zimbabwe"}
\ No newline at end of file
diff --git a/public/country/id-ID.json b/public/country/id-ID.json
new file mode 100644
index 00000000..13a6220a
--- /dev/null
+++ b/public/country/id-ID.json
@@ -0,0 +1 @@
+{"AF":"Afganistan","ZA":"Afrika Selatan","AL":"Albania","DZ":"Aljazair","US":"Amerika Serikat","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktika","AG":"Antigua dan Barbuda","SA":"Arab Saudi","AR":"Argentina","AM":"Armenia","AW":"Aruba","AU":"Australia","AT":"Austria","AZ":"Azerbaijan","BS":"Bahama","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","NL":"Belanda","BQ":"Belanda Karibia","BY":"Belarus","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia dan Herzegovina","BW":"Botswana","BR":"Brasil","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","TD":"Cad","CZ":"Ceko","CL":"Cile","CI":"C\u00f4te d\u2019Ivoire","CW":"Cura\u00e7ao","DK":"Denmark","DM":"Dominika","EC":"Ekuador","SV":"El Salvador","ER":"Eritrea","EE":"Estonia","SZ":"eSwatini","ET":"Etiopia","FJ":"Fiji","PH":"Filipina","FI":"Finlandia","GA":"Gabon","GM":"Gambia","GE":"Georgia","GS":"Georgia Selatan & Kep. Sandwich Selatan","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Grinlandia","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GQ":"Guinea Ekuatorial","GW":"Guinea-Bissau","GY":"Guyana","GF":"Guyana Prancis","HT":"Haiti","HN":"Honduras","HK":"Hong Kong DAK Tiongkok","HU":"Hungaria","IN":"India","ID":"Indonesia","GB":"Inggris Raya","IQ":"Irak","IR":"Iran","IE":"Irlandia","IS":"Islandia","IL":"Israel","IT":"Italia","JM":"Jamaika","JP":"Jepang","DE":"Jerman","JE":"Jersey","DJ":"Jibuti","NC":"Kaledonia Baru","KH":"Kamboja","CM":"Kamerun","CA":"Kanada","KZ":"Kazakstan","KE":"Kenya","AX":"Kepulauan Aland","KY":"Kepulauan Cayman","CC":"Kepulauan Cocos (Keeling)","CK":"Kepulauan Cook","FK":"Kepulauan Falkland","FO":"Kepulauan Faroe","MP":"Kepulauan Mariana Utara","MH":"Kepulauan Marshall","NF":"Kepulauan Norfolk","PN":"Kepulauan Pitcairn","SB":"Kepulauan Solomon","SJ":"Kepulauan Svalbard dan Jan Mayen","UM":"Kepulauan Terluar A.S.","TC":"Kepulauan Turks dan Caicos","VI":"Kepulauan Virgin Amerika Serikat","VG":"Kepulauan Virgin Britania Raya","WF":"Kepulauan Wallis dan Futuna","KG":"Kirgistan","KI":"Kiribati","CO":"Kolombia","KM":"Komoro","CG":"Kongo - Brazzaville","CD":"Kongo - Kinshasa","KR":"Korea Selatan","KP":"Korea Utara","CR":"Kosta Rika","HR":"Kroasia","CU":"Kuba","KW":"Kuwait","LA":"Laos","LV":"Latvia","LB":"Lebanon","LS":"Lesotho","LR":"Liberia","LY":"Libia","LI":"Liechtenstein","LT":"Lituania","LU":"Luksemburg","MG":"Madagaskar","MO":"Makau DAK Tiongkok","MK":"Makedonia Utara","MV":"Maladewa","MW":"Malawi","MY":"Malaysia","ML":"Mali","MT":"Malta","MA":"Maroko","MQ":"Martinik","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Meksiko","EG":"Mesir","FM":"Mikronesia","MD":"Moldova","MC":"Monako","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mozambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NE":"Niger","NG":"Nigeria","NI":"Nikaragua","NU":"Niue","NO":"Norwegia","OM":"Oman","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Nugini","PY":"Paraguay","PE":"Peru","PL":"Polandia","PF":"Polinesia Prancis","PT":"Portugal","FR":"Prancis","PR":"Puerto Riko","BV":"Pulau Bouvet","HM":"Pulau Heard dan Kepulauan McDonald","IM":"Pulau Man","CX":"Pulau Natal","QA":"Qatar","CF":"Republik Afrika Tengah","DO":"Republik Dominika","RE":"R\u00e9union","RO":"Rumania","RU":"Rusia","RW":"Rwanda","EH":"Sahara Barat","BL":"Saint Barth\u00e9lemy","SH":"Saint Helena","KN":"Saint Kitts dan Nevis","LC":"Saint Lucia","MF":"Saint Martin","PM":"Saint Pierre dan Miquelon","VC":"Saint Vincent dan Grenadine","WS":"Samoa","AS":"Samoa Amerika","SM":"San Marino","ST":"Sao Tome dan Principe","NZ":"Selandia Baru","SN":"Senegal","RS":"Serbia","SC":"Seychelles","SL":"Sierra Leone","SG":"Singapura","SX":"Sint Maarten","CY":"Siprus","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","ES":"Spanyol","LK":"Sri Lanka","SD":"Sudan","SS":"Sudan Selatan","SY":"Suriah","SR":"Suriname","SE":"Swedia","CH":"Swiss","TW":"Taiwan","TJ":"Tajikistan","CV":"Tanjung Verde","TZ":"Tanzania","TH":"Thailand","TL":"Timor Leste","CN":"Tiongkok","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad dan Tobago","TN":"Tunisia","TR":"Turki","TM":"Turkimenistan","TV":"Tuvalu","UG":"Uganda","UA":"Ukraina","AE":"Uni Emirat Arab","UY":"Uruguay","UZ":"Uzbekistan","VU":"Vanuatu","VA":"Vatikan","VE":"Venezuela","VN":"Vietnam","IO":"Wilayah Inggris di Samudra Hindia","PS":"Wilayah Palestina","TF":"Wilayah Selatan Perancis","YE":"Yaman","JO":"Yordania","GR":"Yunani","ZM":"Zambia","ZW":"Zimbabwe"}
\ No newline at end of file
diff --git a/public/country/nb-NO.json b/public/country/nb-NO.json
new file mode 100644
index 00000000..89449329
--- /dev/null
+++ b/public/country/nb-NO.json
@@ -0,0 +1 @@
+{"AF":"Afghanistan","AL":"Albania","DZ":"Algerie","AS":"Amerikansk Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua og Barbuda","AR":"Argentina","AM":"Armenia","AW":"Aruba","AZ":"Aserbajdsjan","AU":"Australia","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia-Hercegovina","BW":"Botswana","BV":"Bouvet\u00f8ya","BR":"Brasil","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","CA":"Canada","KY":"Cayman\u00f8yene","CL":"Chile","CX":"Christmas\u00f8ya","CO":"Colombia","CK":"Cook\u00f8yene","CR":"Costa Rica","CU":"Cuba","CW":"Cura\u00e7ao","DK":"Danmark","VI":"De amerikanske jomfru\u00f8yene","VG":"De britiske jomfru\u00f8yene","AE":"De forente arabiske emirater","TF":"De franske s\u00f8rterritorier","DO":"Den dominikanske republikk","CF":"Den sentralafrikanske republikk","IO":"Det britiske territoriet i Indiahavet","PS":"Det palestinske omr\u00e5det","DJ":"Djibouti","DM":"Dominica","EC":"Ecuador","EG":"Egypt","GQ":"Ekvatorial-Guinea","SV":"El Salvador","CI":"Elfenbenskysten","ER":"Eritrea","EE":"Estland","SZ":"Eswatini","ET":"Etiopia","FK":"Falklands\u00f8yene","FJ":"Fiji","PH":"Filippinene","FI":"Finland","FR":"Frankrike","GF":"Fransk Guyana","PF":"Fransk Polynesia","FO":"F\u00e6r\u00f8yene","GA":"Gabon","GM":"Gambia","GE":"Georgia","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Gr\u00f8nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard- og McDonald\u00f8yene","GR":"Hellas","HN":"Honduras","HK":"Hongkong S.A.R. Kina","BY":"Hviterussland","IN":"India","ID":"Indonesia","IQ":"Irak","IR":"Iran","IE":"Irland","IS":"Island","IL":"Israel","IT":"Italia","JM":"Jamaica","JP":"Japan","YE":"Jemen","JE":"Jersey","JO":"Jordan","KH":"Kambodsja","CM":"Kamerun","CV":"Kapp Verde","BQ":"Karibisk Nederland","KZ":"Kasakhstan","KE":"Kenya","CN":"Kina","KG":"Kirgisistan","KI":"Kiribati","CC":"Kokos\u00f8yene","KM":"Komorene","CG":"Kongo-Brazzaville","CD":"Kongo-Kinshasa","HR":"Kroatia","KW":"Kuwait","CY":"Kypros","LA":"Laos","LV":"Latvia","LS":"Lesotho","LB":"Libanon","LR":"Liberia","LY":"Libya","LI":"Liechtenstein","LT":"Litauen","LU":"Luxemburg","MO":"Macao S.A.R. Kina","MG":"Madagaskar","MW":"Malawi","MY":"Malaysia","MV":"Maldivene","ML":"Mali","MT":"Malta","IM":"Man","MA":"Marokko","MH":"Marshall\u00f8yene","MQ":"Martinique","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Mexico","FM":"Mikronesiaf\u00f8derasjonen","MD":"Moldova","MC":"Monaco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mosambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NL":"Nederland","NP":"Nepal","NZ":"New Zealand","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Nord-Korea","MK":"Nord-Makedonia","MP":"Nord-Marianene","NF":"Norfolk\u00f8ya","NO":"Norge","NC":"Ny-Caledonia","OM":"Oman","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Ny-Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn\u00f8yene","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Romania","RU":"Russland","RW":"Rwanda","KN":"Saint Kitts og Nevis","BL":"Saint-Barth\u00e9lemy","MF":"Saint-Martin","PM":"Saint-Pierre-et-Miquelon","SB":"Salomon\u00f8yene","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 og Pr\u00edncipe","SA":"Saudi-Arabia","SN":"Senegal","RS":"Serbia","SC":"Seychellene","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","ES":"Spania","LK":"Sri Lanka","SH":"St. Helena","LC":"St. Lucia","VC":"St. Vincent og Grenadinene","GB":"Storbritannia","SD":"Sudan","SR":"Surinam","SJ":"Svalbard og Jan Mayen","CH":"Sveits","SE":"Sverige","SY":"Syria","ZA":"S\u00f8r-Afrika","GS":"S\u00f8r-Georgia og S\u00f8r-Sandwich\u00f8yene","KR":"S\u00f8r-Korea","SS":"S\u00f8r-Sudan","TJ":"Tadsjikistan","TW":"Taiwan","TZ":"Tanzania","TH":"Thailand","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad og Tobago","TD":"Tsjad","CZ":"Tsjekkia","TN":"Tunisia","TM":"Turkmenistan","TC":"Turks- og Caicos\u00f8yene","TV":"Tuvalu","TR":"Tyrkia","DE":"Tyskland","UG":"Uganda","UA":"Ukraina","HU":"Ungarn","UY":"Uruguay","US":"USA","UM":"USAs ytre \u00f8yer","UZ":"Usbekistan","VU":"Vanuatu","VA":"Vatikanstaten","VE":"Venezuela","EH":"Vest-Sahara","VN":"Vietnam","WF":"Wallis og Futuna","ZM":"Zambia","ZW":"Zimbabwe","TL":"\u00d8st-Timor","AT":"\u00d8sterrike","AX":"\u00c5land"}
\ No newline at end of file
diff --git a/public/country/uk-UA.json b/public/country/uk-UA.json
new file mode 100644
index 00000000..b2605d87
--- /dev/null
+++ b/public/country/uk-UA.json
@@ -0,0 +1 @@
+{"AU":"\u0410\u0432\u0441\u0442\u0440\u0430\u043b\u0456\u044f","AT":"\u0410\u0432\u0441\u0442\u0440\u0456\u044f","AZ":"\u0410\u0437\u0435\u0440\u0431\u0430\u0439\u0434\u0436\u0430\u043d","AX":"\u0410\u043b\u0430\u043d\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","AL":"\u0410\u043b\u0431\u0430\u043d\u0456\u044f","DZ":"\u0410\u043b\u0436\u0438\u0440","AS":"\u0410\u043c\u0435\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0435 \u0421\u0430\u043c\u043e\u0430","AO":"\u0410\u043d\u0433\u043e\u043b\u0430","AI":"\u0410\u043d\u0491\u0456\u043b\u044c\u044f","AD":"\u0410\u043d\u0434\u043e\u0440\u0440\u0430","AQ":"\u0410\u043d\u0442\u0430\u0440\u043a\u0442\u0438\u043a\u0430","AG":"\u0410\u043d\u0442\u0438\u0491\u0443\u0430 \u0456 \u0411\u0430\u0440\u0431\u0443\u0434\u0430","AR":"\u0410\u0440\u0433\u0435\u043d\u0442\u0438\u043d\u0430","AW":"\u0410\u0440\u0443\u0431\u0430","AF":"\u0410\u0444\u0433\u0430\u043d\u0456\u0441\u0442\u0430\u043d","BS":"\u0411\u0430\u0433\u0430\u043c\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BD":"\u0411\u0430\u043d\u0433\u043b\u0430\u0434\u0435\u0448","BB":"\u0411\u0430\u0440\u0431\u0430\u0434\u043e\u0441","BH":"\u0411\u0430\u0445\u0440\u0435\u0439\u043d","BZ":"\u0411\u0435\u043b\u0456\u0437","BE":"\u0411\u0435\u043b\u044c\u0491\u0456\u044f","BJ":"\u0411\u0435\u043d\u0456\u043d","BM":"\u0411\u0435\u0440\u043c\u0443\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BY":"\u0411\u0456\u043b\u043e\u0440\u0443\u0441\u044c","BG":"\u0411\u043e\u043b\u0433\u0430\u0440\u0456\u044f","BO":"\u0411\u043e\u043b\u0456\u0432\u0456\u044f","BA":"\u0411\u043e\u0441\u043d\u0456\u044f \u0456 \u0413\u0435\u0440\u0446\u0435\u0491\u043e\u0432\u0438\u043d\u0430","BW":"\u0411\u043e\u0442\u0441\u0432\u0430\u043d\u0430","BR":"\u0411\u0440\u0430\u0437\u0456\u043b\u0456\u044f","IO":"\u0411\u0440\u0438\u0442\u0430\u043d\u0441\u044c\u043a\u0430 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u044f \u0432 \u0406\u043d\u0434\u0456\u0439\u0441\u044c\u043a\u043e\u043c\u0443 \u041e\u043a\u0435\u0430\u043d\u0456","VG":"\u0411\u0440\u0438\u0442\u0430\u043d\u0441\u044c\u043a\u0456 \u0412\u0456\u0440\u0433\u0456\u043d\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438","BN":"\u0411\u0440\u0443\u043d\u0435\u0439","BF":"\u0411\u0443\u0440\u043a\u0456\u043d\u0430-\u0424\u0430\u0441\u043e","BI":"\u0411\u0443\u0440\u0443\u043d\u0434\u0456","BT":"\u0411\u0443\u0442\u0430\u043d","VU":"\u0412\u0430\u043d\u0443\u0430\u0442\u0443","VA":"\u0412\u0430\u0442\u0438\u043a\u0430\u043d","GB":"\u0412\u0435\u043b\u0438\u043a\u0430 \u0411\u0440\u0438\u0442\u0430\u043d\u0456\u044f","VE":"\u0412\u0435\u043d\u0435\u0441\u0443\u0435\u043b\u0430","UM":"\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438 \u0421\u0428\u0410","VI":"\u0412\u0456\u0440\u0433\u0456\u043d\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438, \u0421\u0428\u0410","AM":"\u0412\u0456\u0440\u043c\u0435\u043d\u0456\u044f","VN":"\u0412\u02bc\u0454\u0442\u043d\u0430\u043c","GA":"\u0413\u0430\u0431\u043e\u043d","HT":"\u0413\u0430\u0457\u0442\u0456","GM":"\u0413\u0430\u043c\u0431\u0456\u044f","GH":"\u0413\u0430\u043d\u0430","GN":"\u0413\u0432\u0456\u043d\u0435\u044f","GW":"\u0413\u0432\u0456\u043d\u0435\u044f-\u0411\u0456\u0441\u0430\u0443","HN":"\u0413\u043e\u043d\u0434\u0443\u0440\u0430\u0441","HK":"\u0413\u043e\u043d\u043a\u043e\u043d\u0433, \u041e.\u0410.\u0420. \u041a\u0438\u0442\u0430\u044e","GR":"\u0413\u0440\u0435\u0446\u0456\u044f","GE":"\u0413\u0440\u0443\u0437\u0456\u044f","GY":"\u0490\u0430\u0439\u0430\u043d\u0430","GP":"\u0490\u0432\u0430\u0434\u0435\u043b\u0443\u043f\u0430","GT":"\u0490\u0432\u0430\u0442\u0435\u043c\u0430\u043b\u0430","GG":"\u0490\u0435\u0440\u043d\u0441\u0456","GI":"\u0490\u0456\u0431\u0440\u0430\u043b\u0442\u0430\u0440","GD":"\u0490\u0440\u0435\u043d\u0430\u0434\u0430","GL":"\u0490\u0440\u0435\u043d\u043b\u0430\u043d\u0434\u0456\u044f","GU":"\u0490\u0443\u0430\u043c","DK":"\u0414\u0430\u043d\u0456\u044f","JE":"\u0414\u0436\u0435\u0440\u0441\u0456","DJ":"\u0414\u0436\u0438\u0431\u0443\u0442\u0456","DM":"\u0414\u043e\u043c\u0456\u043d\u0456\u043a\u0430","DO":"\u0414\u043e\u043c\u0456\u043d\u0456\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","EC":"\u0415\u043a\u0432\u0430\u0434\u043e\u0440","GQ":"\u0415\u043a\u0432\u0430\u0442\u043e\u0440\u0456\u0430\u043b\u044c\u043d\u0430 \u0413\u0432\u0456\u043d\u0435\u044f","ER":"\u0415\u0440\u0438\u0442\u0440\u0435\u044f","SZ":"\u0415\u0441\u0432\u0430\u0442\u0456\u043d\u0456","EE":"\u0415\u0441\u0442\u043e\u043d\u0456\u044f","ET":"\u0415\u0444\u0456\u043e\u043f\u0456\u044f","EG":"\u0404\u0433\u0438\u043f\u0435\u0442","YE":"\u0404\u043c\u0435\u043d","ZM":"\u0417\u0430\u043c\u0431\u0456\u044f","EH":"\u0417\u0430\u0445\u0456\u0434\u043d\u0430 \u0421\u0430\u0445\u0430\u0440\u0430","ZW":"\u0417\u0456\u043c\u0431\u0430\u0431\u0432\u0435","IL":"\u0406\u0437\u0440\u0430\u0457\u043b\u044c","IN":"\u0406\u043d\u0434\u0456\u044f","ID":"\u0406\u043d\u0434\u043e\u043d\u0435\u0437\u0456\u044f","IQ":"\u0406\u0440\u0430\u043a","IR":"\u0406\u0440\u0430\u043d","IE":"\u0406\u0440\u043b\u0430\u043d\u0434\u0456\u044f","IS":"\u0406\u0441\u043b\u0430\u043d\u0434\u0456\u044f","ES":"\u0406\u0441\u043f\u0430\u043d\u0456\u044f","IT":"\u0406\u0442\u0430\u043b\u0456\u044f","JO":"\u0419\u043e\u0440\u0434\u0430\u043d\u0456\u044f","CV":"\u041a\u0430\u0431\u043e-\u0412\u0435\u0440\u0434\u0435","KZ":"\u041a\u0430\u0437\u0430\u0445\u0441\u0442\u0430\u043d","KY":"\u041a\u0430\u0439\u043c\u0430\u043d\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","KH":"\u041a\u0430\u043c\u0431\u043e\u0434\u0436\u0430","CM":"\u041a\u0430\u043c\u0435\u0440\u0443\u043d","CA":"\u041a\u0430\u043d\u0430\u0434\u0430","QA":"\u041a\u0430\u0442\u0430\u0440","KE":"\u041a\u0435\u043d\u0456\u044f","KG":"\u041a\u0438\u0440\u0433\u0438\u0437\u0441\u0442\u0430\u043d","CN":"\u041a\u0438\u0442\u0430\u0439","CY":"\u041a\u0456\u043f\u0440","KI":"\u041a\u0456\u0440\u0456\u0431\u0430\u0442\u0456","CC":"\u041a\u043e\u043a\u043e\u0441\u043e\u0432\u0456 (\u041a\u0456\u043b\u0456\u043d\u0491) \u041e\u0441\u0442\u0440\u043e\u0432\u0438","CO":"\u041a\u043e\u043b\u0443\u043c\u0431\u0456\u044f","KM":"\u041a\u043e\u043c\u043e\u0440\u0438","CG":"\u041a\u043e\u043d\u0433\u043e \u2013 \u0411\u0440\u0430\u0437\u0437\u0430\u0432\u0456\u043b\u044c","CD":"\u041a\u043e\u043d\u0433\u043e \u2013 \u041a\u0456\u043d\u0448\u0430\u0441\u0430","CR":"\u041a\u043e\u0441\u0442\u0430-\u0420\u0456\u043a\u0430","CI":"\u041a\u043e\u0442-\u0434\u02bc\u0406\u0432\u0443\u0430\u0440","CU":"\u041a\u0443\u0431\u0430","KW":"\u041a\u0443\u0432\u0435\u0439\u0442","CW":"\u041a\u044e\u0440\u0430\u0441\u0430\u043e","LA":"\u041b\u0430\u043e\u0441","LV":"\u041b\u0430\u0442\u0432\u0456\u044f","LS":"\u041b\u0435\u0441\u043e\u0442\u043e","LT":"\u041b\u0438\u0442\u0432\u0430","LR":"\u041b\u0456\u0431\u0435\u0440\u0456\u044f","LB":"\u041b\u0456\u0432\u0430\u043d","LY":"\u041b\u0456\u0432\u0456\u044f","LI":"\u041b\u0456\u0445\u0442\u0435\u043d\u0448\u0442\u0435\u0439\u043d","LU":"\u041b\u044e\u043a\u0441\u0435\u043c\u0431\u0443\u0440\u0491","MR":"\u041c\u0430\u0432\u0440\u0438\u0442\u0430\u043d\u0456\u044f","MU":"\u041c\u0430\u0432\u0440\u0456\u043a\u0456\u0439","MG":"\u041c\u0430\u0434\u0430\u0433\u0430\u0441\u043a\u0430\u0440","YT":"\u041c\u0430\u0439\u043e\u0442\u0442\u0430","MO":"\u041c\u0430\u043a\u0430\u043e, \u041e.\u0410.\u0420 \u041a\u0438\u0442\u0430\u044e","MW":"\u041c\u0430\u043b\u0430\u0432\u0456","MY":"\u041c\u0430\u043b\u0430\u0439\u0437\u0456\u044f","ML":"\u041c\u0430\u043b\u0456","MV":"\u041c\u0430\u043b\u044c\u0434\u0456\u0432\u0438","MT":"\u041c\u0430\u043b\u044c\u0442\u0430","MA":"\u041c\u0430\u0440\u043e\u043a\u043a\u043e","MQ":"\u041c\u0430\u0440\u0442\u0456\u043d\u0456\u043a\u0430","MH":"\u041c\u0430\u0440\u0448\u0430\u043b\u043b\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","MX":"\u041c\u0435\u043a\u0441\u0438\u043a\u0430","FM":"\u041c\u0456\u043a\u0440\u043e\u043d\u0435\u0437\u0456\u044f","MZ":"\u041c\u043e\u0437\u0430\u043c\u0431\u0456\u043a","MD":"\u041c\u043e\u043b\u0434\u043e\u0432\u0430","MC":"\u041c\u043e\u043d\u0430\u043a\u043e","MN":"\u041c\u043e\u043d\u0433\u043e\u043b\u0456\u044f","MS":"\u041c\u043e\u043d\u0442\u0441\u0435\u0440\u0440\u0430\u0442","MM":"\u041c\u02bc\u044f\u043d\u043c\u0430 (\u0411\u0456\u0440\u043c\u0430)","NA":"\u041d\u0430\u043c\u0456\u0431\u0456\u044f","NR":"\u041d\u0430\u0443\u0440\u0443","NP":"\u041d\u0435\u043f\u0430\u043b","NE":"\u041d\u0456\u0433\u0435\u0440","NG":"\u041d\u0456\u0433\u0435\u0440\u0456\u044f","NL":"\u041d\u0456\u0434\u0435\u0440\u043b\u0430\u043d\u0434\u0438","BQ":"\u041d\u0456\u0434\u0435\u0440\u043b\u0430\u043d\u0434\u0441\u044c\u043a\u0456 \u041a\u0430\u0440\u0438\u0431\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438","NI":"\u041d\u0456\u043a\u0430\u0440\u0430\u0491\u0443\u0430","DE":"\u041d\u0456\u043c\u0435\u0447\u0447\u0438\u043d\u0430","NU":"\u041d\u0456\u0443\u0435","NZ":"\u041d\u043e\u0432\u0430 \u0417\u0435\u043b\u0430\u043d\u0434\u0456\u044f","NC":"\u041d\u043e\u0432\u0430 \u041a\u0430\u043b\u0435\u0434\u043e\u043d\u0456\u044f","NO":"\u041d\u043e\u0440\u0432\u0435\u0491\u0456\u044f","AE":"\u041e\u0431\u02bc\u0454\u0434\u043d\u0430\u043d\u0456 \u0410\u0440\u0430\u0431\u0441\u044c\u043a\u0456 \u0415\u043c\u0456\u0440\u0430\u0442\u0438","OM":"\u041e\u043c\u0430\u043d","BV":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0411\u0443\u0432\u0435","IM":"\u041e\u0441\u0442\u0440\u0456\u0432 \u041c\u0435\u043d","NF":"\u041e\u0441\u0442\u0440\u0456\u0432 \u041d\u043e\u0440\u0444\u043e\u043b\u043a","CX":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0420\u0456\u0437\u0434\u0432\u0430","SH":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0421\u0432\u044f\u0442\u043e\u0457 \u0404\u043b\u0435\u043d\u0438","HM":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u0413\u0435\u0440\u0434 \u0456 \u041c\u0430\u043a\u0434\u043e\u043d\u0430\u043b\u0434","CK":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u041a\u0443\u043a\u0430","PN":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u041f\u0456\u0442\u043a\u0435\u0440\u043d","TC":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u0422\u0435\u0440\u043a\u0441 \u0456 \u041a\u0430\u0439\u043a\u043e\u0441","PK":"\u041f\u0430\u043a\u0438\u0441\u0442\u0430\u043d","PW":"\u041f\u0430\u043b\u0430\u0443","PS":"\u041f\u0430\u043b\u0435\u0441\u0442\u0438\u043d\u0441\u044c\u043a\u0456 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u0457","PA":"\u041f\u0430\u043d\u0430\u043c\u0430","PG":"\u041f\u0430\u043f\u0443\u0430-\u041d\u043e\u0432\u0430 \u0490\u0432\u0456\u043d\u0435\u044f","PY":"\u041f\u0430\u0440\u0430\u0491\u0432\u0430\u0439","PE":"\u041f\u0435\u0440\u0443","GS":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0430 \u0414\u0436\u043e\u0440\u0434\u0436\u0456\u044f \u0442\u0430 \u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0456 \u0421\u0430\u043d\u0434\u0432\u0456\u0447\u0435\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","KR":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0430 \u041a\u043e\u0440\u0435\u044f","SS":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0438\u0439 \u0421\u0443\u0434\u0430\u043d","ZA":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u043e-\u0410\u0444\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","KP":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0430 \u041a\u043e\u0440\u0435\u044f","MK":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0430 \u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0456\u044f","MP":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0456 \u041c\u0430\u0440\u0456\u0430\u043d\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","PL":"\u041f\u043e\u043b\u044c\u0449\u0430","PT":"\u041f\u043e\u0440\u0442\u0443\u0491\u0430\u043b\u0456\u044f","PR":"\u041f\u0443\u0435\u0440\u0442\u043e-\u0420\u0456\u043a\u043e","RE":"\u0420\u0435\u044e\u043d\u044c\u0439\u043e\u043d","RU":"\u0420\u043e\u0441\u0456\u044f","RW":"\u0420\u0443\u0430\u043d\u0434\u0430","RO":"\u0420\u0443\u043c\u0443\u043d\u0456\u044f","SV":"\u0421\u0430\u043b\u044c\u0432\u0430\u0434\u043e\u0440","WS":"\u0421\u0430\u043c\u043e\u0430","SM":"\u0421\u0430\u043d-\u041c\u0430\u0440\u0456\u043d\u043e","ST":"\u0421\u0430\u043d-\u0422\u043e\u043c\u0435 \u0456 \u041f\u0440\u0456\u043d\u0441\u0456\u043f\u0456","SA":"\u0421\u0430\u0443\u0434\u0456\u0432\u0441\u044c\u043a\u0430 \u0410\u0440\u0430\u0432\u0456\u044f","SC":"\u0421\u0435\u0439\u0448\u0435\u043b\u044c\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BL":"\u0421\u0435\u043d-\u0411\u0430\u0440\u0442\u0435\u043b\u044c\u043c\u0456","MF":"\u0421\u0435\u043d-\u041c\u0430\u0440\u0442\u0435\u043d","PM":"\u0421\u0435\u043d-\u041f\u02bc\u0454\u0440 \u0456 \u041c\u0456\u043a\u0435\u043b\u043e\u043d","SN":"\u0421\u0435\u043d\u0435\u0433\u0430\u043b","VC":"\u0421\u0435\u043d\u0442-\u0412\u0456\u043d\u0441\u0435\u043d\u0442 \u0456 \u0490\u0440\u0435\u043d\u0430\u0434\u0456\u043d\u0438","KN":"\u0421\u0435\u043d\u0442-\u041a\u0456\u0442\u0441 \u0456 \u041d\u0435\u0432\u0456\u0441","LC":"\u0421\u0435\u043d\u0442-\u041b\u044e\u0441\u0456\u044f","RS":"\u0421\u0435\u0440\u0431\u0456\u044f","SY":"\u0421\u0438\u0440\u0456\u044f","SG":"\u0421\u0456\u043d\u0433\u0430\u043f\u0443\u0440","SX":"\u0421\u0456\u043d\u0442-\u041c\u0430\u0440\u0442\u0435\u043d","SK":"\u0421\u043b\u043e\u0432\u0430\u0447\u0447\u0438\u043d\u0430","SI":"\u0421\u043b\u043e\u0432\u0435\u043d\u0456\u044f","SB":"\u0421\u043e\u043b\u043e\u043c\u043e\u043d\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","SO":"\u0421\u043e\u043c\u0430\u043b\u0456","US":"\u0421\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0456 \u0428\u0442\u0430\u0442\u0438","SD":"\u0421\u0443\u0434\u0430\u043d","SR":"\u0421\u0443\u0440\u0456\u043d\u0430\u043c","SL":"\u0421\u044c\u0454\u0440\u0440\u0430-\u041b\u0435\u043e\u043d\u0435","TJ":"\u0422\u0430\u0434\u0436\u0438\u043a\u0438\u0441\u0442\u0430\u043d","TH":"\u0422\u0430\u0457\u043b\u0430\u043d\u0434","TW":"\u0422\u0430\u0439\u0432\u0430\u043d\u044c","TZ":"\u0422\u0430\u043d\u0437\u0430\u043d\u0456\u044f","TL":"\u0422\u0456\u043c\u043e\u0440-\u041b\u0435\u0448\u0442\u0456","TG":"\u0422\u043e\u0433\u043e","TK":"\u0422\u043e\u043a\u0435\u043b\u0430\u0443","TO":"\u0422\u043e\u043d\u0491\u0430","TT":"\u0422\u0440\u0456\u043d\u0456\u0434\u0430\u0434 \u0456 \u0422\u043e\u0431\u0430\u0491\u043e","TV":"\u0422\u0443\u0432\u0430\u043b\u0443","TN":"\u0422\u0443\u043d\u0456\u0441","TR":"\u0422\u0443\u0440\u0435\u0447\u0447\u0438\u043d\u0430","TM":"\u0422\u0443\u0440\u043a\u043c\u0435\u043d\u0456\u0441\u0442\u0430\u043d","UG":"\u0423\u0433\u0430\u043d\u0434\u0430","HU":"\u0423\u0433\u043e\u0440\u0449\u0438\u043d\u0430","UZ":"\u0423\u0437\u0431\u0435\u043a\u0438\u0441\u0442\u0430\u043d","UA":"\u0423\u043a\u0440\u0430\u0457\u043d\u0430","WF":"\u0423\u043e\u043b\u043b\u0456\u0441 \u0456 \u0424\u0443\u0442\u0443\u043d\u0430","UY":"\u0423\u0440\u0443\u0491\u0432\u0430\u0439","FO":"\u0424\u0430\u0440\u0435\u0440\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","FJ":"\u0424\u0456\u0434\u0436\u0456","PH":"\u0424\u0456\u043b\u0456\u043f\u043f\u0456\u043d\u0438","FI":"\u0424\u0456\u043d\u043b\u044f\u043d\u0434\u0456\u044f","FK":"\u0424\u043e\u043b\u043a\u043b\u0435\u043d\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","FR":"\u0424\u0440\u0430\u043d\u0446\u0456\u044f","GF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0430 \u0490\u0432\u0456\u0430\u043d\u0430","PF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0430 \u041f\u043e\u043b\u0456\u043d\u0435\u0437\u0456\u044f","TF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0456 \u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0456 \u0422\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u0457","HR":"\u0425\u043e\u0440\u0432\u0430\u0442\u0456\u044f","CF":"\u0426\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u0430\u0444\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","TD":"\u0427\u0430\u0434","CZ":"\u0427\u0435\u0445\u0456\u044f","CL":"\u0427\u0456\u043b\u0456","ME":"\u0427\u043e\u0440\u043d\u043e\u0433\u043e\u0440\u0456\u044f","CH":"\u0428\u0432\u0435\u0439\u0446\u0430\u0440\u0456\u044f","SE":"\u0428\u0432\u0435\u0446\u0456\u044f","SJ":"\u0428\u043f\u0456\u0446\u0431\u0435\u0440\u0433\u0435\u043d \u0442\u0430 \u042f\u043d-\u041c\u0430\u0454\u043d","LK":"\u0428\u0440\u0456-\u041b\u0430\u043d\u043a\u0430","JM":"\u042f\u043c\u0430\u0439\u043a\u0430","JP":"\u042f\u043f\u043e\u043d\u0456\u044f"}
\ No newline at end of file
diff --git a/redux/actions/app.js b/redux/actions/app.js
index 490bd7f1..f10d162a 100644
--- a/redux/actions/app.js
+++ b/redux/actions/app.js
@@ -1,16 +1,25 @@
import { createSlice } from '@reduxjs/toolkit';
import { getItem } from 'lib/web';
-import { LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants';
+import {
+ DEFAULT_LOCALE,
+ DEFAULT_THEME,
+ LOCALE_CONFIG,
+ THEME_CONFIG,
+ VERSION_CHECK,
+} from 'lib/constants';
+import semver from 'semver';
const app = createSlice({
name: 'app',
initialState: {
- locale: getItem(LOCALE_CONFIG) || 'en-US',
- theme: getItem(THEME_CONFIG) || 'light',
+ locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
+ theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
versions: {
current: process.env.VERSION,
latest: null,
+ hasUpdate: false,
},
+ shareToken: null,
},
reducers: {
setLocale(state, action) {
@@ -25,10 +34,14 @@ const app = createSlice({
state.versions = action.payload;
return state;
},
+ setShareToken(state, action) {
+ state.shareToken = action.payload;
+ return state;
+ },
},
});
-export const { setLocale, setTheme, setVersions } = app.actions;
+export const { setLocale, setTheme, setVersions, setShareToken } = app.actions;
export default app.reducer;
@@ -60,11 +73,14 @@ export function checkVersion() {
const { tag_name } = data;
const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name;
+ const lastCheck = getItem(VERSION_CHECK);
+ const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
return dispatch(
setVersions({
current,
latest,
+ hasUpdate,
}),
);
};
diff --git a/scripts/check-lang.js b/scripts/check-lang.js
index 96522879..a8c54538 100644
--- a/scripts/check-lang.js
+++ b/scripts/check-lang.js
@@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const messages = require('../lang/en-US.json');
+const ignore = require('./lang-ignore.json');
const dir = path.resolve(__dirname, '../lang');
const files = fs.readdirSync(dir);
@@ -10,13 +11,15 @@ const keys = Object.keys(messages).sort();
files.forEach(file => {
if (file !== 'en-US.json') {
const lang = require(`../lang/${file}`);
+ const id = file.replace('.json', '');
console.log(chalk.yellowBright(`\n## ${file}`));
keys.forEach(key => {
const orig = messages[key];
const check = lang[key];
+ const ignored = ignore[id]?.includes(key);
- if (!check || check === orig) {
+ if (!ignored && (!check || check === orig)) {
console.log(chalk.redBright('*'), chalk.greenBright(`${key}:`), orig);
}
});
diff --git a/scripts/download-country-names.js b/scripts/download-country-names.js
new file mode 100644
index 00000000..4ee90cd4
--- /dev/null
+++ b/scripts/download-country-names.js
@@ -0,0 +1,39 @@
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+const chalk = require('chalk');
+
+const src = path.resolve(__dirname, '../lang');
+const dest = path.resolve(__dirname, '../public/country');
+const files = fs.readdirSync(src);
+
+const getUrl = locale =>
+ `https://raw.githubusercontent.com/umpirsky/country-list/master/data/${locale}/country.json`;
+
+const asyncForEach = async (array, callback) => {
+ for (let index = 0; index < array.length; index++) {
+ await callback(array[index], index, array);
+ }
+};
+
+if (!fs.existsSync(dest)) {
+ fs.mkdirSync(dest);
+}
+
+const download = async files => {
+ await asyncForEach(files, async file => {
+ const locale = file.replace('-', '_').replace('.json', '');
+
+ const filename = path.join(dest, file);
+ if (!fs.existsSync(filename)) {
+ await new Promise(resolve => {
+ https.get(getUrl(locale), res => {
+ console.log('Downloaded', chalk.greenBright('->'), filename);
+ resolve(res.pipe(fs.createWriteStream(filename)));
+ });
+ });
+ }
+ });
+};
+
+download(files);
diff --git a/scripts/lang-ignore.json b/scripts/lang-ignore.json
new file mode 100644
index 00000000..5e33c85a
--- /dev/null
+++ b/scripts/lang-ignore.json
@@ -0,0 +1,11 @@
+{
+ "de-DE": [
+ "label.administrator",
+ "label.name",
+ "metrics.device.desktop",
+ "metrics.device.laptop",
+ "metrics.device.tablet",
+ "metrics.referrers"
+ ],
+ "fr-FR": ["metrics.actions", "metrics.pages"]
+}
diff --git a/scripts/loadtest.js b/scripts/loadtest.js
new file mode 100644
index 00000000..e2146dd0
--- /dev/null
+++ b/scripts/loadtest.js
@@ -0,0 +1,140 @@
+const loadtest = require('loadtest');
+const chalk = require('chalk');
+const trunc = num => +num.toFixed(1);
+
+/**
+ * Example invocations:
+ *
+ * npm run loadtest -- --weight=heavy
+ * npm run loadtest -- --weight=heavy --verbose
+ * npm run loadtest -- --weight=single --verbose
+ * npm run loadtest -- --weight=medium
+ */
+
+/**
+ * Command line arguments like --weight=heavy and --verbose use this object
+ * If you are providing _alternative_ configs, use --weight
+ * e.g. add --weight=ultra then add commandlineOptions.ultra={}
+ * --verbose can be combied with any weight.
+ */
+const commandlineOptions = {
+ single: {
+ concurrency: 1,
+ requestsPerSecond: 1,
+ maxSeconds: 5,
+ maxRequests: 1,
+ },
+ // Heavy can saturate CPU which leads to requests stalling depending on machine
+ // Keep an eye if --verbose logs pause, or if node CPU in top is > 100.
+ // https://github.com/alexfernandez/loadtest#usage-donts
+ heavy: {
+ concurrency: 10,
+ requestsPerSecond: 200,
+ maxSeconds: 60,
+ },
+ // Throttled requests should not max out CPU,
+ medium: {
+ concurrency: 3,
+ requestsPerSecond: 5,
+ maxSeconds: 60,
+ },
+ verbose: { statusCallback },
+};
+
+const options = {
+ url: 'http://localhost:3000',
+ method: 'POST',
+ concurrency: 5,
+ requestsPerSecond: 5,
+ maxSeconds: 5,
+ requestGenerator: (params, options, client, callback) => {
+ const message = JSON.stringify(mockPageView());
+ options.headers['Content-Length'] = message.length;
+ options.headers['Content-Type'] = 'application/json';
+ options.headers['user-agent'] = 'User-Agent: Mozilla/5.0 LoadTest';
+ options.body = message;
+ options.path = '/api/collect';
+ const request = client(options, callback);
+ request.write(message);
+ return request;
+ },
+};
+
+function getArgument() {
+ const weight = process.argv[2] && process.argv[2].replace('--weight=', '');
+ const verbose = process.argv.includes('--verbose') && 'verbose';
+ return [weight, verbose];
+}
+
+// Patch in all command line arguments over options object
+// Must do this prior to calling `loadTest()`
+getArgument().map(arg => Object.assign(options, commandlineOptions[arg]));
+
+loadtest.loadTest(options, (error, results) => {
+ if (error) {
+ return console.error(chalk.redBright('Got an error: %s', error));
+ }
+ console.log(chalk.bold(chalk.yellow('\n--------\n')));
+ console.log(chalk.yellowBright('Loadtests complete:'), chalk.greenBright('success'), '\n');
+ prettyLogItem('Total Requests:', results.totalRequests);
+ prettyLogItem('Total Errors:', results.totalErrors);
+
+ prettyLogItem(
+ 'Latency(mean/min/max)',
+ trunc(results.meanLatencyMs),
+ '/',
+ trunc(results.maxLatencyMs),
+ '/',
+ trunc(results.minLatencyMs),
+ );
+
+ if (results.totalErrors) {
+ console.log(chalk.redBright('*'), chalk.red('Total Errors:'), results.totalErrors);
+ }
+
+ if (results.errorCodes && Object.keys(results.errorCodes).length) {
+ console.log(chalk.redBright('*'), chalk.red('Error Codes:'), results.errorCodes);
+ }
+ // console.log(results);
+});
+
+/**
+ * Create a new object for each request. Note, we could randomize values here if desired.
+ *
+ * TODO: Need a better way of passing in websiteId, hostname, URL.
+ *
+ * @param {object} payload pageview payload same as sent via tracker
+ */
+function mockPageView(
+ payload = {
+ website: 'fcd4c7e3-ed76-439c-9121-3a0f102df126',
+ hostname: 'localhost',
+ screen: '1680x1050',
+ url: '/LOADTESTING',
+ },
+) {
+ return {
+ type: 'pageview',
+ payload,
+ };
+}
+
+// If you pass in --verbose, this function is called
+function statusCallback(error, result, latency) {
+ console.log(
+ chalk.yellowBright(`\n## req #${result.requestIndex + 1} of ${latency.totalRequests}`),
+ );
+ prettyLogItem('Request elapsed milliseconds:', trunc(result.requestElapsed));
+ prettyLogItem(
+ 'Latency(mean/max/min):',
+ trunc(latency.meanLatencyMs),
+ '/',
+ trunc(latency.maxLatencyMs),
+ '/',
+ trunc(latency.minLatencyMs),
+ );
+}
+
+function prettyLogItem(label, ...args) {
+ console.log(chalk.redBright('*'), chalk.green(label), ...args);
+}
diff --git a/scripts/start-env.js b/scripts/start-env.js
index 63b66369..05823b7a 100644
--- a/scripts/start-env.js
+++ b/scripts/start-env.js
@@ -1,3 +1,3 @@
const cli = require('next/dist/cli/next-start');
-cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || '0.0.0.0']);
+cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || 'localhost']);
diff --git a/styles/index.css b/styles/index.css
index c0e2ae6d..c695a2cf 100644
--- a/styles/index.css
+++ b/styles/index.css
@@ -37,6 +37,9 @@ h4,
h5,
h6 {
font-weight: 400;
+ line-height: 30px;
+ padding: 0;
+ margin: 0;
}
button,
diff --git a/tracker/index.js b/tracker/index.js
index 12119298..ff0ce28d 100644
--- a/tracker/index.js
+++ b/tracker/index.js
@@ -6,19 +6,30 @@ import { removeTrailingSlash } from '../lib/url';
screen: { width, height },
navigator: { language },
location: { hostname, pathname, search },
+ sessionStorage,
document,
history,
} = window;
const script = document.querySelector('script[data-website-id]');
- const attr = key => script && script.getAttribute(key);
+ if (!script) return;
+
+ const attr = key => script && script.getAttribute(key);
const website = attr('data-website-id');
const hostUrl = attr('data-host-url');
const autoTrack = attr('data-auto-track') !== 'false';
- const dnt = attr('data-do-not-track') === 'true';
+ const dnt = attr('data-do-not-track');
+ const useCache = attr('data-cache');
+ const domains = attr('data-domains');
- if (!script || (dnt && doNotTrack())) return;
+ const disableTracking =
+ (dnt && doNotTrack()) ||
+ (domains &&
+ !domains
+ .split(',')
+ .map(n => n.trim())
+ .includes(hostname));
const root = hostUrl
? removeTrailingSlash(hostUrl)
@@ -37,7 +48,7 @@ import { removeTrailingSlash } from '../lib/url';
req.onreadystatechange = () => {
if (req.readyState === 4) {
- callback && callback();
+ callback && callback(req.response);
}
};
@@ -45,11 +56,16 @@ import { removeTrailingSlash } from '../lib/url';
};
const collect = (type, params, uuid) => {
+ if (disableTracking) return;
+
+ const key = 'umami.cache';
+
const payload = {
website: uuid,
hostname,
screen,
language,
+ cache: useCache && sessionStorage.getItem(key),
};
if (params) {
@@ -58,10 +74,14 @@ import { removeTrailingSlash } from '../lib/url';
});
}
- return post(`${root}/api/collect`, {
- type,
- payload,
- });
+ post(
+ `${root}/api/collect`,
+ {
+ type,
+ payload,
+ },
+ res => useCache && sessionStorage.setItem(key, res),
+ );
};
const trackView = (url = currentUrl, referrer = currentRef, uuid = website) =>
@@ -90,7 +110,7 @@ import { removeTrailingSlash } from '../lib/url';
const addEvents = () => {
document.querySelectorAll("[class*='umami--']").forEach(element => {
element.className.split(' ').forEach(className => {
- if (/^umami--([a-z]+)--([a-z0-9_]+[a-z0-9-_]+)$/.test(className)) {
+ if (/^umami--([a-z]+)--([\w]+[\w-]*)$/.test(className)) {
const [, type, value] = className.split('--');
const listener = () => trackEvent(value, type);
@@ -140,7 +160,7 @@ import { removeTrailingSlash } from '../lib/url';
/* Start */
- if (autoTrack) {
+ if (autoTrack && !disableTracking) {
history.pushState = hook(history, 'pushState', handlePush);
history.replaceState = hook(history, 'replaceState', handlePush);
diff --git a/yarn.lock b/yarn.lock
index 00d976eb..70310b2e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -398,7 +398,7 @@
"@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
-"@babel/plugin-proposal-nullish-coalescing-operator@7.10.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4":
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a"
integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==
@@ -431,7 +431,7 @@
"@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
-"@babel/plugin-proposal-optional-chaining@7.11.0", "@babel/plugin-proposal-optional-chaining@^7.11.0":
+"@babel/plugin-proposal-optional-chaining@^7.11.0":
version "7.11.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076"
integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==
@@ -498,7 +498,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-jsx@^7.10.4":
+"@babel/plugin-syntax-jsx@7.10.4", "@babel/plugin-syntax-jsx@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c"
integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==
@@ -1064,12 +1064,12 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
-"@formatjs/cli@^2.12.0":
- version "2.12.0"
- resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-2.12.0.tgz#f0bb253db073903634e57e587e0395cd0d0cd681"
- integrity sha512-F0epNBWCXjKGgej8GL1q4RLGqR38bRCPmGLb3VautkbZ74achB0cVGj2w/AdlQiJJ1mU5rEU13pRroukUBZ+GA==
+"@formatjs/cli@^2.13.2":
+ version "2.13.2"
+ resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-2.13.2.tgz#08402f07eb6c6cb626ccd33e304012f30c1fb345"
+ integrity sha512-o0jY7AmaXIHugiV35xhKeNYQ9pzTi0O+stfZqY0+Sqb3MFk6oLz/K5ExvnXHvf3soiCOat4UhzAxHSR9T0QWwQ==
dependencies:
- "@formatjs/ts-transformer" "^2.11.0"
+ "@formatjs/ts-transformer" "^2.11.3"
"@types/json-stable-stringify" "^1.0.32"
"@types/lodash" "^4.14.150"
"@types/loud-rejection" "^2.0.0"
@@ -1078,10 +1078,11 @@
commander "^6.1.0"
fast-glob "^3.2.4"
fs-extra "^9.0.0"
- intl-messageformat-parser "^6.0.7"
+ intl-messageformat-parser "^6.0.10"
json-stable-stringify "^1.0.1"
lodash "^4.17.15"
loud-rejection "^2.2.0"
+ tslib "^2.0.1"
typescript "^4.0"
"@formatjs/ecma402-abstract@^1.2.2":
@@ -1089,19 +1090,35 @@
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.2.tgz#4810bdbd696d3805c535fd0620b7c8f45ab3164f"
integrity sha512-mLCoAPGlXCVskb/ojBO6iurGqwo6sZvAl8pRC4N25bz4LPWExAM9LsOo057zN3Br1JxUM3RZHG4YGnVt+nSRYQ==
-"@formatjs/intl-displaynames@^3.3.9":
- version "3.3.9"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.9.tgz#18eeb39aa05d4a8b064e520725f9178d0c3f8c50"
- integrity sha512-6Ez9Ab9p9bsxCM4OlqsT+R0rmrj5lr6xjIXiCTs/pSDFeiNPQabWDHcBpiGlMRE3zifOwUOFSoi5AGGYMFgetw==
+"@formatjs/ecma402-abstract@^1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.3.tgz#ca94911dd8e9c89eeaabba0f00e2f692979fc27b"
+ integrity sha512-sEkxTZj7qa+Pi/c4ppE5mxZYJkqQO3GNZzJZjAxgsXip2ixO/TZn58qrxdQ0V8mXmf+5xf+AfveyPvv4yHaRtw==
dependencies:
- "@formatjs/ecma402-abstract" "^1.2.2"
+ tslib "^2.0.1"
-"@formatjs/intl-listformat@^4.2.7":
- version "4.2.7"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.7.tgz#bcccfee92c69e661d4da51567540aa39fef9ceae"
- integrity sha512-7sYy7pBGzClvSZI98FhVeNt6N/ELdvrj8pvLOfcR0+FQyhv0ixanog7bRo9kT8ECin9+RwEpTmX7jGCh8Bcgjw==
+"@formatjs/ecma402-abstract@^1.2.5":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.5.tgz#5a61ac1990ff2df8d1348ab12e186c1ca2a2bd71"
+ integrity sha512-k0fqS3LBNOHueAoMdgig8Ni6TchsH+zbzWBzX2gTFm50X9mxHwnuXdCk0XLlCIbvgVVlzcO254Men/mHAheMbg==
dependencies:
- "@formatjs/ecma402-abstract" "^1.2.2"
+ tslib "^2.0.1"
+
+"@formatjs/intl-displaynames@^3.3.10":
+ version "3.3.10"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.10.tgz#905ad86431fdadfab2ec188bf9f4fe9e359d1fe6"
+ integrity sha512-SdIMuaKUO0N5zQb6CXtIrwjJbX+DC8ju7ifrcqpLagUMh2nIEJCz7sf0Q6lOMWEE+un1VTmjaXpRPP55cP40IA==
+ dependencies:
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ tslib "^2.0.1"
+
+"@formatjs/intl-listformat@^4.2.8":
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.8.tgz#aa8935234dd5f8fcec6d08dfdf2be43e11ba671c"
+ integrity sha512-9qAThh/1HV9T/g6E11VbN5b209zg28fMUMrZqrpHiZZxc2PPHvP/CGqK7mo8hpyCoMUVo3kFxB5CFnw5difJrA==
+ dependencies:
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ tslib "^2.0.1"
"@formatjs/intl-numberformat@^5.5.2":
version "5.6.2"
@@ -1110,32 +1127,35 @@
dependencies:
"@formatjs/ecma402-abstract" "^1.2.2"
-"@formatjs/intl-relativetimeformat@^7.2.7":
- version "7.2.7"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.7.tgz#dce72ebafb1ca0bf14673fab3b1aad145e1abac6"
- integrity sha512-R6rxX4PfPQ/EuDRALLSIEDCDW/FAmmu6xpRXIPdZ33bEP7SXEksJB039Bw//I0VIm2fBeIlxe9oqvRGfuqpwVg==
+"@formatjs/intl-relativetimeformat@^7.2.8":
+ version "7.2.8"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.8.tgz#a423ef9acd379980f58730b4757713156076de14"
+ integrity sha512-h6H5lcPn1LbVlAk62m7DYtY68aE6AiZVK1bLEo3HeWrMBFCskWAe9I/5kI+RjStdGZzo+CqBl+rSTcrSXjVj+g==
dependencies:
- "@formatjs/ecma402-abstract" "^1.2.2"
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ tslib "^2.0.1"
-"@formatjs/intl@^1.3.3":
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.3.tgz#d59997b7ef832e7c47e8ca50861ff7ff7461d56c"
- integrity sha512-gFuCIZEH6o1O2ZF8YlhHJEApRrBarQ7iyqxFp4ujllr/tcjgSxfzF+LSBCPyJ1OQIU98ynOc0XKdrAR2wUd3ow==
+"@formatjs/intl@^1.3.4":
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.4.tgz#66441b85986726256f83fdffdb533c6334bc299b"
+ integrity sha512-aq6bhi2aZPYUEL15iiBrsNzDtw4Qe1r9dsqM26fbTbfWa6r5sdqcFwGySoeTzguxd+ZXoc9RypSMERjH92xFKA==
dependencies:
- "@formatjs/ecma402-abstract" "^1.2.2"
- "@formatjs/intl-displaynames" "^3.3.9"
- "@formatjs/intl-listformat" "^4.2.7"
- "@formatjs/intl-relativetimeformat" "^7.2.7"
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ "@formatjs/intl-displaynames" "^3.3.10"
+ "@formatjs/intl-listformat" "^4.2.8"
+ "@formatjs/intl-relativetimeformat" "^7.2.8"
fast-memoize "^2.5.2"
- intl-messageformat "^9.3.8"
- intl-messageformat-parser "^6.0.7"
+ intl-messageformat "^9.3.9"
+ intl-messageformat-parser "^6.0.8"
+ tslib "^2.0.1"
-"@formatjs/ts-transformer@^2.11.0":
- version "2.11.0"
- resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.11.0.tgz#90c4b5afae55fd8b8c7ab6aa08ca94a123eb94b9"
- integrity sha512-d0++zpEeeCtE+RwbEB+TYw0WnC+jlNniIZu9NcILdgN6LEr9+TRxO+Gz4d7nj3g0D5X1LyNx6P4JI+byGxHqzw==
+"@formatjs/ts-transformer@^2.11.3":
+ version "2.11.3"
+ resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.11.3.tgz#9d253ba4681f1654dddbfb43e453d0692f8c85c9"
+ integrity sha512-nt51rmoeDnORFXGkJGX3tpC1oYY6WDkJTpOCCsYkRxWxmgY1kT5v8wckNB/CvBmz4egFNShr1iRNDa7o+EsKZA==
dependencies:
- intl-messageformat-parser "^6.0.7"
+ intl-messageformat-parser "^6.0.10"
+ tslib "^2.0.1"
typescript "^4.0"
"@formatjs/ts-transformer@^2.6.0":
@@ -1146,10 +1166,40 @@
intl-messageformat-parser "^6.0.7"
typescript "^4.0"
-"@next/react-dev-overlay@9.5.3":
- version "9.5.3"
- resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.3.tgz#3275301f08045ecc709e3273031973a1f5e81427"
- integrity sha512-R2ZAyFjHHaMTBVi19ZZNRJNXiwn46paRi7EZvKNvMxbrzBcUYtSFj/edU3jQoF1UOcC6vGeMhtPqH55ONrIjCQ==
+"@hapi/accept@5.0.1":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10"
+ integrity sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q==
+ dependencies:
+ "@hapi/boom" "9.x.x"
+ "@hapi/hoek" "9.x.x"
+
+"@hapi/boom@9.x.x":
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.0.tgz#0d9517657a56ff1e0b42d0aca9da1b37706fec56"
+ integrity sha512-4nZmpp4tXbm162LaZT45P7F7sgiem8dwAh2vHWT6XX24dozNjGMg6BvKCRvtCUcmcXqeMIUqWN8Rc5X8yKuROQ==
+ dependencies:
+ "@hapi/hoek" "9.x.x"
+
+"@hapi/hoek@9.x.x":
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6"
+ integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==
+
+"@next/env@9.5.5":
+ version "9.5.5"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-9.5.5.tgz#db993649ec6e619e34a36de90dc2baa52fc5280f"
+ integrity sha512-N9wdjU6XoqLqNQWtrGiWtp1SUuJsYK1cNrZ24A6YD+4w5CNV5SkZX6aewKZCCLP5Y8UNfTij2FkJiSYUfBjX8g==
+
+"@next/polyfill-module@9.5.5":
+ version "9.5.5"
+ resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-9.5.5.tgz#d9c65679a66664ab4859078f58997113c9d01f10"
+ integrity sha512-itqYFeHo3yN4ccpHq2uNFC2UVQm12K6DxUVwYdui9MJiiueT0pSGb2laYEjf/G5+vVq7M2vb+DkjkOkPMBVfeg==
+
+"@next/react-dev-overlay@9.5.5":
+ version "9.5.5"
+ resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.5.tgz#11b36813d75c43b7bd9d5e478bded1ed5391d03a"
+ integrity sha512-B1nDANxjXr2oyohv+tX0OXZTmJtO5qEWmisNPGnqQ2Z32IixfaAgyNYVuCVf20ap6EUz5elhgNUwRIFh/e26mQ==
dependencies:
"@babel/code-frame" "7.10.4"
ally.js "1.4.1"
@@ -1162,10 +1212,10 @@
stacktrace-parser "0.1.10"
strip-ansi "6.0.0"
-"@next/react-refresh-utils@9.5.3":
- version "9.5.3"
- resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.3.tgz#a14fb6489d412b201b98aa44716fb8727ca4c6ae"
- integrity sha512-W3VKOqbg+4Kw+k6M/SODf+WIzwcx60nAemGV1nNPa/yrDtAS2YcJfqiswrJ3+2nJHzqefAFWn4XOfM0fy8ww2Q==
+"@next/react-refresh-utils@9.5.5":
+ version "9.5.5"
+ resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.5.tgz#fe559b5ca51c038cb7840e0d669a6d7ef01fe4eb"
+ integrity sha512-Gz5z0+ID+KAGto6Tkgv1a340damEw3HG6ANLKwNi5/QSHqQ3JUAVxMuhz3qnL54505I777evpzL89ofWEMIWKw==
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
@@ -1188,20 +1238,27 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
+"@npmcli/move-file@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464"
+ integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==
+ dependencies:
+ mkdirp "^1.0.4"
+
"@panva/asn1.js@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
-"@prisma/cli@2.8.0":
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.8.0.tgz#919d7f66023affa76d14823212b62a8512cfd37d"
- integrity sha512-Kg1C47d75jdEIMmJif8TMlv/2Ihx08E1qWp0euwoZhjd807HGnjgC9tJYjTfkdf+NMJSAUbvoPXKInEX0HoOMw==
+"@prisma/cli@2.9.0":
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.9.0.tgz#c6f6652890783b899f266537e90e69c3481f5474"
+ integrity sha512-wPk4ehyTtVM7ZarWs16MhOc6kwLV/gZFardMvUeh46rlBwrklMdKtNChzzPa3wurrUPQ5KTbuRBz5Mgf7AdD/w==
-"@prisma/client@2.8.0":
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.8.0.tgz#a0f7247786c9b6ee804437acf8215854c5eb3946"
- integrity sha512-5+GzRTkPnmv4OEV2tB8kwQt/xLLxBR/daJBcMt6pnnonJvrREsu0tSTdz2LJNPaj3kTT0fSS/OaeGMMdfVYSpw==
+"@prisma/client@2.9.0":
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.9.0.tgz#acfaca0c5695d7f439e5af54b7cd0f7fb5926340"
+ integrity sha512-VLXw6s13xakIrV9Z8Ftw0j+mbXuvbRkxYv3X5hRZdPN1NAwgeXJmrrTK9MS2HDvW8eGZL7P8OaUSNQ3Sfgvr/Q==
dependencies:
pkg-up "^3.1.0"
@@ -1773,6 +1830,18 @@ agent-base@6:
dependencies:
debug "4"
+agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
+agentkeepalive@^2.0.3:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz#c5d1bd4b129008f1163f236f86e5faea2026e2ef"
+ integrity sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=
+
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -1791,7 +1860,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.4:
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4:
version "6.12.5"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
@@ -2309,6 +2378,13 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
+bufferutil@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7"
+ integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==
+ dependencies:
+ node-gyp-build "~3.7.0"
+
builtin-modules@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
@@ -2319,28 +2395,27 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
-cacache@13.0.1:
- version "13.0.1"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c"
- integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==
+cacache@15.0.5:
+ version "15.0.5"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0"
+ integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==
dependencies:
- chownr "^1.1.2"
- figgy-pudding "^3.5.1"
+ "@npmcli/move-file" "^1.0.1"
+ chownr "^2.0.0"
fs-minipass "^2.0.0"
glob "^7.1.4"
- graceful-fs "^4.2.2"
infer-owner "^1.0.4"
- lru-cache "^5.1.1"
- minipass "^3.0.0"
+ lru-cache "^6.0.0"
+ minipass "^3.1.1"
minipass-collect "^1.0.2"
minipass-flush "^1.0.5"
minipass-pipeline "^1.2.2"
- mkdirp "^0.5.1"
- move-concurrently "^1.0.1"
- p-map "^3.0.0"
+ mkdirp "^1.0.3"
+ p-map "^4.0.0"
promise-inflight "^1.0.1"
- rimraf "^2.7.1"
- ssri "^7.0.0"
+ rimraf "^3.0.2"
+ ssri "^8.0.0"
+ tar "^6.0.2"
unique-filename "^1.1.1"
cacache@^12.0.2:
@@ -2536,7 +2611,7 @@ chokidar@^3.4.1:
optionalDependencies:
fsevents "~2.1.2"
-chownr@^1.1.1, chownr@^1.1.2:
+chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
@@ -2617,15 +2692,6 @@ clone-deep@^0.2.4:
lazy-cache "^1.0.3"
shallow-clone "^0.1.2"
-clone-deep@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
- integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
- dependencies:
- is-plain-object "^2.0.4"
- kind-of "^6.0.2"
- shallow-clone "^3.0.0"
-
clone-regexp@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
@@ -2746,6 +2812,14 @@ concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+confinode@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/confinode/-/confinode-2.1.1.tgz#6831961ca48fb3c9f7d5ff063022e2bc40bea86e"
+ integrity sha512-u5u0ZHpYMnVWtelxjalNtLvL+SdP7B/7s0JTFUIkyvqqIf67DAvy6SKaE6WZiwbufLPk+6zJKsh5SdpbtbFi9g==
+ dependencies:
+ quick-lru "^5.0.0"
+ yaml "^1.7.2"
+
console-browserify@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
@@ -2935,24 +3009,23 @@ css-has-pseudo@^0.10.0:
postcss "^7.0.6"
postcss-selector-parser "^5.0.0-rc.4"
-css-loader@3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.5.3.tgz#95ac16468e1adcd95c844729e0bb167639eb0bcf"
- integrity sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw==
+css-loader@4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e"
+ integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==
dependencies:
- camelcase "^5.3.1"
+ camelcase "^6.0.0"
cssesc "^3.0.0"
icss-utils "^4.1.1"
- loader-utils "^1.2.3"
- normalize-path "^3.0.0"
- postcss "^7.0.27"
+ loader-utils "^2.0.0"
+ postcss "^7.0.32"
postcss-modules-extract-imports "^2.0.0"
- postcss-modules-local-by-default "^3.0.2"
+ postcss-modules-local-by-default "^3.0.3"
postcss-modules-scope "^2.2.0"
postcss-modules-values "^3.0.0"
- postcss-value-parser "^4.0.3"
- schema-utils "^2.6.6"
- semver "^6.3.0"
+ postcss-value-parser "^4.1.0"
+ schema-utils "^2.7.1"
+ semver "^7.3.2"
css-prefers-color-scheme@^3.1.1:
version "3.1.1"
@@ -3172,16 +3245,21 @@ data-uri-to-buffer@3.0.0:
dependencies:
buffer-from "^1.1.1"
-date-fns-tz@^1.0.10:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.10.tgz#30fef0038f80534fddd8e133a6b8ca55ba313748"
- integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg==
+date-fns-tz@^1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.12.tgz#2d680e1099767775cff7a30eac34362d52639fed"
+ integrity sha512-Ca+9pjGkU90XDHnclfSjz9o7g/ZqyYyYI0aCYmbf65P75oy8gktuaRslO3UPXl3ADgAnF9/KCykQkpU3/xvtWQ==
date-fns@^2.16.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
+debounce@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
+ integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
+
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
@@ -3196,7 +3274,7 @@ debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
-debug@^3.2.6:
+debug@^3.1.0, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -3302,10 +3380,10 @@ des.js@^1.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
-detect-browser@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.1.1.tgz#a800db91d3fd60d0861669f5984f1be9ffbe009c"
- integrity sha512-5n2aWI57qC3kZaK4j2zYsG6L1LrxgLptGCNhMQgdKhVn6cSdcq43pp6xHPfTHG3TYM6myF4tIPWiZtfdVDgb9w==
+detect-browser@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97"
+ integrity sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==
detect-indent@^6.0.0:
version "6.0.0"
@@ -3610,6 +3688,18 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
es6-symbol@^3.1.1, es6-symbol@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
@@ -3647,16 +3737,16 @@ eslint-plugin-react-hooks@^4.1.2:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz#2eb53731d11c95826ef7a7272303eabb5c9a271e"
integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg==
-eslint-plugin-react@^7.21.2:
- version "7.21.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.2.tgz#3bd5d2c4c36d5a0428d0d6dda301ac9a84d681b2"
- integrity sha512-j3XKvrK3rpBzveKFbgAeGsWb9uz6iUOrR0jixRfjwdFeGSRsXvVTFtHDQYCjsd1/6Z/xvb8Vy3LiI5Reo7fDrg==
+eslint-plugin-react@^7.21.4:
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.4.tgz#31060b2e5ff82b12e24a3cc33edb7d12f904775c"
+ integrity sha512-uHeQ8A0hg0ltNDXFu3qSfFqTNPXm1XithH6/SY318UX76CMj7Q599qWpgmMhVQyvhq36pm7qvoN3pb6/3jsTFg==
dependencies:
array-includes "^3.1.1"
array.prototype.flatmap "^1.2.3"
doctrine "^2.1.0"
has "^1.0.3"
- jsx-ast-utils "^2.4.1"
+ jsx-ast-utils "^2.4.1 || ^3.0.0"
object.entries "^1.1.2"
object.fromentries "^2.0.2"
object.values "^1.1.1"
@@ -3699,6 +3789,11 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+eslint-visitor-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+ integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
eslint@^6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
@@ -3742,10 +3837,10 @@ eslint@^6.8.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
-eslint@^7.10.0:
- version "7.10.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.10.0.tgz#494edb3e4750fb791133ca379e786a8f648c72b9"
- integrity sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==
+eslint@^7.11.0:
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b"
+ integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==
dependencies:
"@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.1.3"
@@ -3757,7 +3852,7 @@ eslint@^7.10.0:
enquirer "^2.3.5"
eslint-scope "^5.1.1"
eslint-utils "^2.1.0"
- eslint-visitor-keys "^1.3.0"
+ eslint-visitor-keys "^2.0.0"
espree "^7.3.0"
esquery "^1.2.0"
esutils "^2.0.2"
@@ -4145,10 +4240,10 @@ for-own@^0.1.3:
dependencies:
for-in "^1.0.1"
-formik@^2.1.6:
- version "2.1.6"
- resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.6.tgz#f723bfccb2c7abec886aa6a4930b360d20f1a0b3"
- integrity sha512-m9DcxlZw/58p4xuhH3dzUzQWaC4dig0RKX7yNQOJt4VRhXn7p+YRrs3o17r3YwzvOLua3zC53VMbfupLsDwO5w==
+formik@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.0.tgz#04c067f6b832aa15cae055f6df6f4aa485b5be35"
+ integrity sha512-l47RfvejhfHNh8rTRVaCaPfx8nyeYDSTLaEqRvLX4qkWnrrq9ByGVCWggVR+0TVtzc5Ub1gLUuVu9UKuGwfhjA==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
@@ -4366,7 +4461,7 @@ gonzales-pe@^4.3.0:
dependencies:
minimist "^1.2.5"
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@@ -4524,6 +4619,14 @@ https-proxy-agent@5.0.0:
agent-base "6"
debug "4"
+https-proxy-agent@^2.2.1:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+ integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@@ -4687,6 +4790,14 @@ intl-messageformat-parser@^5.3.7:
dependencies:
"@formatjs/intl-numberformat" "^5.5.2"
+intl-messageformat-parser@^6.0.10:
+ version "6.0.10"
+ resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.10.tgz#6202bd4e9b59eaa5bf34d4db2ef8c2af46d40037"
+ integrity sha512-xURB8PchDsdLEpedzPLzBmGCK996FPBl3yLQIUxcM3EAA3TdhXPArX6GY4LVXQNeq8V4WtwmgxaJOKtdOpA8pg==
+ dependencies:
+ "@formatjs/ecma402-abstract" "^1.2.5"
+ tslib "^2.0.1"
+
intl-messageformat-parser@^6.0.7:
version "6.0.7"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.7.tgz#de57a03466e1441b7cbfd99a02ace108745ef133"
@@ -4694,13 +4805,22 @@ intl-messageformat-parser@^6.0.7:
dependencies:
"@formatjs/ecma402-abstract" "^1.2.2"
-intl-messageformat@^9.3.8:
- version "9.3.8"
- resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.8.tgz#561f31800cc3ced5dada6c991a0dd0233931850f"
- integrity sha512-XuFoC6kvsgL1qtzro9ubOaJ2zVgeJWb5X0mTYvG7p1OinbOZYPscP8eYyVJf9g++tDy/fwx9TfeaFlunmhC+Vw==
+intl-messageformat-parser@^6.0.8:
+ version "6.0.8"
+ resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.8.tgz#4180b280d21653df7c8c078e75e0bb7f0e3322c3"
+ integrity sha512-g1nV8YVI/Nscbu3qjGGgMcq61Es7L2bI+08gcbAx3taiFMJ3oJgQhC/wYksWLsq2cvLxq5pQ5Te06CE793/iVA==
+ dependencies:
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ tslib "^2.0.1"
+
+intl-messageformat@^9.3.9:
+ version "9.3.9"
+ resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.9.tgz#41f9f139adbbb509da657133047c8c5bf4ca22a9"
+ integrity sha512-SB6b68bY+RZPBhg3XTmwfX0lL3ywvOuAcS+iH6cptiHPfzOPSzP05F3ZOirARwj8pVbC9Xd4w0pMtF/sGnHurw==
dependencies:
fast-memoize "^2.5.2"
- intl-messageformat-parser "^6.0.7"
+ intl-messageformat-parser "^6.0.8"
+ tslib "^2.0.1"
invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
@@ -5122,13 +5242,13 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
-jsx-ast-utils@^2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
- integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==
+"jsx-ast-utils@^2.4.1 || ^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.0.0.tgz#0f49d5093bafa4b45d3fe02147d8b40ffc6c7438"
+ integrity sha512-sPuicm6EPKYI/UnWpOatvg4pI50qaBo4dSOMGUPutmJ26ttedFKXr0It0XXPk4HKnQ/1X0st4eSS2w2jhFk9Ow==
dependencies:
array-includes "^3.1.1"
- object.assign "^4.1.0"
+ object.assign "^4.1.1"
kind-of@^2.0.1:
version "2.0.1"
@@ -5161,6 +5281,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+klona@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
+ integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
+
known-css-properties@^0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b"
@@ -5296,6 +5421,19 @@ loader-utils@^1.2.3:
emojis-list "^3.0.0"
json5 "^1.0.1"
+loadtest@5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/loadtest/-/loadtest-5.1.0.tgz#20dd700329d98612b23c7e6fa0d1d4661ea9bccf"
+ integrity sha512-LFMyFMA77o41JyNPn+FOXNN/SNURXys8KQNK83mR6bDHjH/XpA5Uz8dd4lofh2VITrDsK+ITKi8QNkmxTOFt1Q==
+ dependencies:
+ agentkeepalive "^2.0.3"
+ confinode "^2.1.1"
+ https-proxy-agent "^2.2.1"
+ log "1.4.*"
+ stdio "^0.2.3"
+ testing "^1.1.1"
+ websocket "^1.0.28"
+
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -5378,6 +5516,11 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
+log@1.4.*, log@1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/log/-/log-1.4.0.tgz#4ba1d890fde249b031dca03bc37eaaf325656f1c"
+ integrity sha1-S6HYkP3iSbAx3KA7w36q8yVlbxw=
+
loglevel-colored-level-prefix@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e"
@@ -5411,7 +5554,7 @@ loud-rejection@*, loud-rejection@^2.2.0:
currently-unhandled "^0.4.1"
signal-exit "^3.0.2"
-lru-cache@6.0.0:
+lru-cache@6.0.0, lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
@@ -5493,11 +5636,12 @@ mathml-tag-names@^2.1.3:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
-maxmind@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.2.0.tgz#912e5ec4a961807d20d7fb541160aeb5ea802c1c"
- integrity sha512-TADiE11Q10IjvLtlo05tTD52xLqfCJMhE3eYJHmpYIKg668STi/fQZGH9X3FpqpIP/2WPgKFxf899awFvfMtQA==
+maxmind@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.3.0.tgz#0e04f00503e3693615efe848e94ec959500d7573"
+ integrity sha512-qYdCsoivX7xGW8L5UhjZNxx57odwzmzFPtSt3YvGwW8moSKoIwnCq2NbmfHdmqYzj5ffzSzxKM3lqURN8INyzA==
dependencies:
+ mmdb-lib "1.2.0"
tiny-lru "7.0.6"
md5.js@^1.3.4:
@@ -5778,11 +5922,16 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
-mkdirp@^1.0.3:
+mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+mmdb-lib@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mmdb-lib/-/mmdb-lib-1.2.0.tgz#0ecd93f4942f65a2d09be0502fa9126939606727"
+ integrity sha512-3XYebkStxqCgWJjsmT9FCaE19Yi4Tvs2SBPKhUks3rJJh52oF1AKAd9kei+LTutud3a6RCZ0o2Um96Fn7o3zVA==
+
moment-timezone@^0.5.31:
version "0.5.31"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05"
@@ -5870,7 +6019,7 @@ neo-async@2.6.1:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
-neo-async@^2.5.0, neo-async@^2.6.1:
+neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@@ -5880,22 +6029,21 @@ next-tick@~1.0.0:
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
-next@^9.5.3:
- version "9.5.3"
- resolved "https://registry.yarnpkg.com/next/-/next-9.5.3.tgz#7af5270631f98d330a7f75a6e8e1ac202aa155e2"
- integrity sha512-DGrpTNGV2RNMwLaSzpgbkbaUuVk30X71/roXHS10isSXo2Gm+qWcjonDyOxf1KmOvHZRHA/Fa+LaAR7ysdYS3A==
+next@^9.5.5:
+ version "9.5.5"
+ resolved "https://registry.yarnpkg.com/next/-/next-9.5.5.tgz#37a37095e7c877ed6c94ba82e34ab9ed02b4eb33"
+ integrity sha512-KF4MIdTYeI6YIGODNw27w9HGzCll4CXbUpkP6MNvyoHlpsunx8ybkQHm/hYa7lWMozmsn58LwaXJOhe4bSrI0g==
dependencies:
"@ampproject/toolbox-optimizer" "2.6.0"
"@babel/code-frame" "7.10.4"
"@babel/core" "7.7.7"
"@babel/plugin-proposal-class-properties" "7.10.4"
"@babel/plugin-proposal-export-namespace-from" "7.10.4"
- "@babel/plugin-proposal-nullish-coalescing-operator" "7.10.4"
"@babel/plugin-proposal-numeric-separator" "7.10.4"
"@babel/plugin-proposal-object-rest-spread" "7.11.0"
- "@babel/plugin-proposal-optional-chaining" "7.11.0"
"@babel/plugin-syntax-bigint" "7.8.3"
"@babel/plugin-syntax-dynamic-import" "7.8.3"
+ "@babel/plugin-syntax-jsx" "7.10.4"
"@babel/plugin-transform-modules-commonjs" "7.10.4"
"@babel/plugin-transform-runtime" "7.11.5"
"@babel/preset-env" "7.11.5"
@@ -5904,19 +6052,21 @@ next@^9.5.3:
"@babel/preset-typescript" "7.10.4"
"@babel/runtime" "7.11.2"
"@babel/types" "7.11.5"
- "@next/react-dev-overlay" "9.5.3"
- "@next/react-refresh-utils" "9.5.3"
+ "@hapi/accept" "5.0.1"
+ "@next/env" "9.5.5"
+ "@next/polyfill-module" "9.5.5"
+ "@next/react-dev-overlay" "9.5.5"
+ "@next/react-refresh-utils" "9.5.5"
ast-types "0.13.2"
- babel-plugin-syntax-jsx "6.18.0"
babel-plugin-transform-define "2.0.0"
babel-plugin-transform-react-remove-prop-types "0.4.24"
browserslist "4.13.0"
buffer "5.6.0"
- cacache "13.0.1"
+ cacache "15.0.5"
caniuse-lite "^1.0.30001113"
chokidar "2.1.8"
crypto-browserify "3.12.0"
- css-loader "3.5.3"
+ css-loader "4.3.0"
cssnano-simple "1.2.0"
find-cache-dir "3.3.1"
jest-worker "24.9.0"
@@ -5933,15 +6083,15 @@ next@^9.5.3:
react-is "16.13.1"
react-refresh "0.8.3"
resolve-url-loader "3.1.1"
- sass-loader "8.0.2"
- schema-utils "2.6.6"
+ sass-loader "10.0.2"
+ schema-utils "2.7.1"
stream-browserify "3.0.0"
style-loader "1.2.1"
styled-jsx "3.3.0"
use-subscription "1.4.1"
vm-browserify "1.1.2"
watchpack "2.0.0-beta.13"
- web-vitals "0.2.1"
+ web-vitals "0.2.4"
webpack "4.44.1"
webpack-sources "1.4.3"
@@ -5960,6 +6110,11 @@ node-fetch@2.6.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+node-gyp-build@~3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
+ integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
+
node-html-parser@^1.2.19:
version "1.2.20"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.2.20.tgz#37e9ebc627dbe3ff446eea4ac93e3d254b7c6ee4"
@@ -6168,7 +6323,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
-object.assign@^4.1.0:
+object.assign@^4.1.0, object.assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
@@ -6309,13 +6464,6 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
-p-map@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d"
- integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==
- dependencies:
- aggregate-error "^3.0.0"
-
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
@@ -6745,7 +6893,7 @@ postcss-modules-extract-imports@^2.0.0:
dependencies:
postcss "^7.0.5"
-postcss-modules-local-by-default@^3.0.2:
+postcss-modules-local-by-default@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==
@@ -6930,7 +7078,7 @@ postcss-value-parser@^3.2.3:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
-postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0:
+postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
@@ -6962,7 +7110,7 @@ postcss@7.0.32:
source-map "^0.6.1"
supports-color "^6.1.0"
-postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
+postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.34"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20"
integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==
@@ -7125,6 +7273,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+quick-lru@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -7165,22 +7318,23 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
-react-intl@^5.8.3:
- version "5.8.3"
- resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.3.tgz#2018aca11a49d2d1b0f602e0dda653a47bf59dfb"
- integrity sha512-ueM7JhbBIi+6FpH6jCrJuKcYpjmFn9UAHA28ojY8LMAL1PowZ/53XGGMvfj32J0/2EIuHQ6vUbi/07kJqe1ksQ==
+react-intl@^5.8.4:
+ version "5.8.4"
+ resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.4.tgz#aba5432fcba17f47d9d46ac9bea1881c92f4f354"
+ integrity sha512-ToDeHYBpO9WBAOpnwQKihUdxB4qE1nqcGKV9Jq2upj1zspxeX3OddWaZwz8wNV5yjoYnoDY3HLWeBP4IXwbwqg==
dependencies:
- "@formatjs/ecma402-abstract" "^1.2.2"
- "@formatjs/intl" "^1.3.3"
- "@formatjs/intl-displaynames" "^3.3.9"
- "@formatjs/intl-listformat" "^4.2.7"
- "@formatjs/intl-relativetimeformat" "^7.2.7"
+ "@formatjs/ecma402-abstract" "^1.2.3"
+ "@formatjs/intl" "^1.3.4"
+ "@formatjs/intl-displaynames" "^3.3.10"
+ "@formatjs/intl-listformat" "^4.2.8"
+ "@formatjs/intl-relativetimeformat" "^7.2.8"
"@types/hoist-non-react-statics" "^3.3.1"
fast-memoize "^2.5.2"
hoist-non-react-statics "^3.3.2"
- intl-messageformat "^9.3.8"
- intl-messageformat-parser "^6.0.7"
+ intl-messageformat "^9.3.9"
+ intl-messageformat-parser "^6.0.8"
shallow-equal "^1.2.1"
+ tslib "^2.0.1"
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0:
version "16.13.1"
@@ -7229,6 +7383,13 @@ react-tooltip@^4.2.10:
prop-types "^15.7.2"
uuid "^7.0.3"
+react-use-measure@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.0.2.tgz#06b4f99b887d5dfcd7b7167a2da063d97ec8f62f"
+ integrity sha512-/+eSmQiU2ePNTwFCXX4JPrQNMvyu3sWrSDi/n5F6IMXwboB46IvtU8VHvG7Nc+egvtM7sBJKwmUx/vx6KIRDog==
+ dependencies:
+ debounce "^1.2.0"
+
react-window@^1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
@@ -7603,7 +7764,7 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
-rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1:
+rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -7642,10 +7803,10 @@ rollup-plugin-terser@^7.0.2:
serialize-javascript "^4.0.0"
terser "^5.0.0"
-rollup@^2.28.2:
- version "2.28.2"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.28.2.tgz#599ec4978144a82d8a8ec3d37670a8440cb04e4b"
- integrity sha512-8txbsFBFLmm9Xdt4ByTOGa9Muonmc8MfNjnGAR8U8scJlF1ZW7AgNZa7aqBXaKtlvnYP/ab++fQIq9dB9NWUbg==
+rollup@^2.30.0:
+ version "2.30.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.30.0.tgz#316a1eb0389dbda4082ef2d191b31488995e4c41"
+ integrity sha512-j4K1hUZfgFM03DUpayd3c7kZW+2wDbI6rj7ssQxpCpL1vsGpaM0vSorxBuePFwQDFq9O2DI6AOQbm174Awsq4w==
optionalDependencies:
fsevents "~2.1.2"
@@ -7695,16 +7856,16 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sass-loader@8.0.2:
- version "8.0.2"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d"
- integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==
+sass-loader@10.0.2:
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.2.tgz#c7b73010848b264792dd45372eea0b87cba4401e"
+ integrity sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==
dependencies:
- clone-deep "^4.0.1"
- loader-utils "^1.2.3"
- neo-async "^2.6.1"
- schema-utils "^2.6.1"
- semver "^6.3.0"
+ klona "^2.0.3"
+ loader-utils "^2.0.0"
+ neo-async "^2.6.2"
+ schema-utils "^2.7.1"
+ semver "^7.3.2"
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
@@ -7727,7 +7888,7 @@ scheduler@^0.19.1:
loose-envify "^1.1.0"
object-assign "^4.1.1"
-schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6:
+schema-utils@*, schema-utils@2.7.1, schema-utils@^2.6.6, schema-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
@@ -7736,14 +7897,6 @@ schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6:
ajv "^6.12.4"
ajv-keywords "^3.5.2"
-schema-utils@2.6.6:
- version "2.6.6"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c"
- integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==
- dependencies:
- ajv "^6.12.0"
- ajv-keywords "^3.4.1"
-
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@@ -7773,7 +7926,7 @@ semver@7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
-semver@^6.0.0, semver@^6.1.2, semver@^6.3.0:
+semver@^6.0.0, semver@^6.1.2:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -7828,13 +7981,6 @@ shallow-clone@^0.1.2:
lazy-cache "^0.2.3"
mixin-object "^2.0.1"
-shallow-clone@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
- integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
- dependencies:
- kind-of "^6.0.2"
-
shallow-equal@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
@@ -8057,12 +8203,11 @@ ssri@^6.0.1:
dependencies:
figgy-pudding "^3.5.1"
-ssri@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d"
- integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==
+ssri@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808"
+ integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==
dependencies:
- figgy-pudding "^3.5.1"
minipass "^3.1.1"
stable@^0.1.8:
@@ -8090,6 +8235,11 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
+stdio@^0.2.3:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/stdio/-/stdio-0.2.7.tgz#a1c57da10fe1cfaa0c3bf683c9d0743d1b660839"
+ integrity sha1-ocV9oQ/hz6oMO/aDydB0PRtmCDk=
+
stream-browserify@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
@@ -8516,7 +8666,7 @@ tar@^4.4.2:
safe-buffer "^5.1.2"
yallist "^3.0.3"
-tar@^6.0.5:
+tar@^6.0.2, tar@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
@@ -8561,6 +8711,13 @@ terser@^5.0.0:
source-map "~0.6.1"
source-map-support "~0.5.12"
+testing@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/testing/-/testing-1.1.2.tgz#575b123070f63c5068e943cf255dbae71c5d8ba6"
+ integrity sha512-+wHrDL29KsI3NQtgGmgdZ/MaUZhnVePbt5ZfiMn6ntDpv/kMWfdiBrg/lJqntor9H8+zQYxvfPLVowPPs1nVEg==
+ dependencies:
+ log "1.4.0"
+
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -8711,6 +8868,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
+tslib@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
+ integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
+
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
@@ -8964,6 +9126,13 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
+utf-8-validate@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3"
+ integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==
+ dependencies:
+ node-gyp-build "~3.7.0"
+
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -8998,10 +9167,10 @@ uuid@^7.0.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
-uuid@^8.3.0:
- version "8.3.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
- integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
+uuid@^8.3.1:
+ version "8.3.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
+ integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
version "2.1.1"
@@ -9093,10 +9262,10 @@ watchpack@^1.7.4:
chokidar "^3.4.1"
watchpack-chokidar2 "^2.0.0"
-web-vitals@0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.1.tgz#60782fa690243fe35613759a0c26431f57ba7b2d"
- integrity sha512-2pdRlp6gJpOCg0oMMqwFF0axjk5D9WInc09RSYtqFgPXQ15+YKNQ7YnBBEqAL5jvmfH9WvoXDMb8DHwux7pIew==
+web-vitals@0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511"
+ integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==
webidl-conversions@^4.0.2:
version "4.0.2"
@@ -9140,6 +9309,18 @@ webpack@4.44.1:
watchpack "^1.7.4"
webpack-sources "^1.4.1"
+websocket@^1.0.28:
+ version "1.0.32"
+ resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1"
+ integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==
+ dependencies:
+ bufferutil "^4.0.1"
+ debug "^2.2.0"
+ es5-ext "^0.10.50"
+ typedarray-to-buffer "^3.1.5"
+ utf-8-validate "^5.0.2"
+ yaeti "^0.0.6"
+
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
@@ -9240,6 +9421,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+yaeti@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
+ integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=
+
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"