upgrading to 1.22.0
commit
629392d440
|
@ -17,6 +17,7 @@
|
|||
/build
|
||||
/public/umami.js
|
||||
/public/geo
|
||||
/public/lang
|
||||
/lang-compiled
|
||||
|
||||
# misc
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Build image
|
||||
FROM node:12.18-alpine AS build
|
||||
ARG BASE_PATH
|
||||
ARG DATABASE_TYPE
|
||||
ENV BASE_PATH=$BASE_PATH
|
||||
ENV DATABASE_URL "postgresql://umami:umami@db:5432/umami" \
|
||||
DATABASE_TYPE=$DATABASE_TYPE
|
||||
WORKDIR /build
|
||||
|
|
|
@ -10,7 +10,7 @@ A detailed getting started guide can be found at [https://umami.is/docs/](https:
|
|||
|
||||
### Requirements
|
||||
|
||||
- A server with Node.js 10.13 or newer
|
||||
- A server with Node.js 12 or newer
|
||||
- A database (MySQL or Postgresql)
|
||||
|
||||
### Get the source code and install packages
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 6.0.0-alpha1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M424 392H24C11 392 0 403 0 416V416C0 429 11 440 24 440H424C437 440 448 429 448 416V416C448 403 437 392 424 392ZM424 72H24C11 72 0 83 0 96V96C0 109 11 120 24 120H424C437 120 448 109 448 96V96C448 83 437 72 424 72ZM424 232H24C11 232 0 243 0 256V256C0 269 11 280 24 280H424C437 280 448 269 448 256V256C448 243 437 232 424 232Z"/></svg>
|
After Width: | Height: | Size: 546 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Pro 6.0.0-alpha1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M345 375C354 384 354 400 345 409S320 418 311 409L192 290L73 409C64 418 48 418 39 409S30 384 39 375L158 256L39 137C30 128 30 112 39 103S64 94 73 103L192 222L311 103C320 94 336 94 345 103S354 128 345 137L226 256L345 375Z"/></svg>
|
After Width: | Height: | Size: 441 B |
|
@ -27,7 +27,7 @@ import styles from './Calendar.module.css';
|
|||
import Icon from './Icon';
|
||||
|
||||
export default function Calendar({ date, minDate, maxDate, onChange }) {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const [selectMonth, setSelectMonth] = useState(false);
|
||||
const [selectYear, setSelectYear] = useState(false);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ const filterOptions = [
|
|||
];
|
||||
|
||||
function DateFilter({ value, startDate, endDate, onChange, className }) {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const displayValue =
|
||||
value === 'custom' ? (
|
||||
|
@ -102,7 +102,7 @@ function DateFilter({ value, startDate, endDate, onChange, className }) {
|
|||
}
|
||||
|
||||
const CustomRange = ({ startDate, endDate, onClick }) => {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
|
||||
function handleClick(e) {
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -12,7 +12,7 @@ import useLocale from 'hooks/useLocale';
|
|||
|
||||
function RefreshButton({ websiteId }) {
|
||||
const dispatch = useDispatch();
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const completed = useSelector(state => state.queries[`/api/website/${websiteId}/stats`]);
|
||||
|
|
|
@ -24,7 +24,7 @@ function WorldMap({ data, className }) {
|
|||
}),
|
||||
[theme],
|
||||
);
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
|
||||
function getFillColor(code) {
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, {
|
||||
FormButtons,
|
||||
FormError,
|
||||
FormMessage,
|
||||
FormRow,
|
||||
} from 'components/layout/FormLayout';
|
||||
import usePost from 'hooks/usePost';
|
||||
|
||||
const CONFIRMATION_WORD = 'RESET';
|
||||
|
||||
const validate = ({ confirmation }) => {
|
||||
const errors = {};
|
||||
|
||||
if (confirmation !== CONFIRMATION_WORD) {
|
||||
errors.confirmation = !confirmation ? (
|
||||
<FormattedMessage id="label.required" defaultMessage="Required" />
|
||||
) : (
|
||||
<FormattedMessage id="label.invalid" defaultMessage="Invalid" />
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
export default function ResetForm({ values, onSave, onClose }) {
|
||||
const post = usePost();
|
||||
const [message, setMessage] = useState();
|
||||
|
||||
const handleSubmit = async ({ type, id }) => {
|
||||
const { ok, data } = await post(`/api/${type}/${id}/reset`);
|
||||
|
||||
if (ok) {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage(
|
||||
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormLayout>
|
||||
<Formik
|
||||
initialValues={{ confirmation: '', ...values }}
|
||||
validate={validate}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{props => (
|
||||
<Form>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="message.confirm-reset"
|
||||
defaultMessage="Are your sure you want to reset {target}'s statistics?"
|
||||
values={{ target: <b>{values.name}</b> }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="message.reset-warning"
|
||||
defaultMessage="All statistics for this website will be deleted, but your tracking code will remain intact."
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="message.type-reset"
|
||||
defaultMessage="Type {reset} in the box below to confirm."
|
||||
values={{ reset: <b>{CONFIRMATION_WORD}</b> }}
|
||||
/>
|
||||
</p>
|
||||
<FormRow>
|
||||
<div>
|
||||
<Field name="confirmation" type="text" />
|
||||
<FormError name="confirmation" />
|
||||
</div>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="danger"
|
||||
disabled={props.values.confirmation !== CONFIRMATION_WORD}
|
||||
>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="label.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormLayout>
|
||||
);
|
||||
}
|
|
@ -4,11 +4,15 @@ import { FormattedMessage } from 'react-intl';
|
|||
import Link from 'components/common/Link';
|
||||
import styles from './Footer.module.css';
|
||||
import useVersion from 'hooks/useVersion';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { rtlLocales } from 'lib/lang';
|
||||
|
||||
export default function Footer() {
|
||||
const { current } = useVersion();
|
||||
const { locale } = useLocale();
|
||||
|
||||
return (
|
||||
<footer className="container">
|
||||
<footer className="container" dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>
|
||||
<div className={classNames(styles.footer, 'row')}>
|
||||
<div className="col-12 col-md-4" />
|
||||
<div className="col-12 col-md-4">
|
||||
|
|
|
@ -8,19 +8,25 @@ import LanguageButton from 'components/settings/LanguageButton';
|
|||
import ThemeButton from 'components/settings/ThemeButton';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import UserButton from 'components/settings/UserButton';
|
||||
import Button from 'components/common/Button';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Header.module.css';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { rtlLocales } from 'lib/lang';
|
||||
import XMark from 'assets/xmark.svg';
|
||||
import Bars from 'assets/bars.svg';
|
||||
|
||||
export default function Header() {
|
||||
const user = useSelector(state => state.user);
|
||||
const [active, setActive] = useState(false);
|
||||
const { locale } = useLocale();
|
||||
|
||||
function handleClick() {
|
||||
setActive(state => !state);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="container">
|
||||
<nav className="container" dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>
|
||||
{user?.is_admin && <UpdateNotice />}
|
||||
<div className={classNames(styles.header, 'row align-items-center')}>
|
||||
<div className={styles.nav}>
|
||||
|
@ -30,23 +36,11 @@ export default function Header() {
|
|||
<Link href={user ? '/' : 'https://umami.is'}>umami</Link>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
role="button"
|
||||
<Button
|
||||
className={styles.burger}
|
||||
aria-label="menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{active ? (
|
||||
<div> X </div>
|
||||
) : (
|
||||
<>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
icon={active ? <XMark /> : <Bars />}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
{user && (
|
||||
<div className={styles.items}>
|
||||
<div className={active ? classNames(styles.active) : ''}>
|
||||
|
|
|
@ -86,7 +86,6 @@
|
|||
.items {
|
||||
display: flex;
|
||||
justify-content: unset;
|
||||
align-items: left;
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -106,34 +105,12 @@
|
|||
|
||||
.burger {
|
||||
display: block;
|
||||
/* color: #4a4a4a; */
|
||||
background: none;
|
||||
border: 1px solid var(--gray900);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
height: 3.25rem;
|
||||
width: 3.25rem;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.burger span {
|
||||
transform: translateX(25%);
|
||||
padding: 1px 0px;
|
||||
margin: 6px 0;
|
||||
width: 65%;
|
||||
display: block;
|
||||
background-color: var(--gray900);
|
||||
}
|
||||
|
||||
.burger div {
|
||||
/* height: 100%; */
|
||||
color: var(--gray900);
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
font-size: 1.5rem;
|
||||
/* transform: translateX(-50%); */
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,25 @@ import React from 'react';
|
|||
import Head from 'next/head';
|
||||
import Header from 'components/layout/Header';
|
||||
import Footer from 'components/layout/Footer';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { rtlLocales } from 'lib/lang';
|
||||
|
||||
export default function Layout({ title, children, header = true, footer = true }) {
|
||||
const { locale } = useLocale();
|
||||
const dir = rtlLocales.includes(locale) ? 'rtl' : 'ltr';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>umami{title && ` - ${title}`}</title>
|
||||
</Head>
|
||||
|
||||
{header && <Header />}
|
||||
<main className="container">{children}</main>
|
||||
<main className="container" dir={dir}>
|
||||
{children}
|
||||
</main>
|
||||
{footer && <Footer />}
|
||||
<div id="__modals" />
|
||||
<div id="__modals" dir={dir} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,25 @@ import React from 'react';
|
|||
import classNames from 'classnames';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
export default function Page({ className, children }) {
|
||||
return <div className={classNames(styles.page, className)}>{children}</div>;
|
||||
export default class Page extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
if (window.pageXOffset === 0 && window.pageYOffset === 0) return null;
|
||||
|
||||
// Return the scrolled position as the snapshot value
|
||||
return { x: window.pageXOffset, y: window.pageYOffset };
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (snapshot !== null) {
|
||||
// Restore the scrolled position after re-rendering
|
||||
window.scrollTo(snapshot.x, snapshot.y);
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
render() {
|
||||
const { className, children } = this.props;
|
||||
return <div className={classNames(styles.page, className)}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export default function BarChart({
|
|||
const canvas = useRef();
|
||||
const chart = useRef();
|
||||
const [tooltip, setTooltip] = useState(null);
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const [theme] = useTheme();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import useCountryNames from 'hooks/useCountryNames';
|
|||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
|
||||
function renderLabel({ x }) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import styles from './Legend.module.css';
|
|||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
|
||||
export default function Legend({ chart }) {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
function handleClick(index) {
|
||||
|
|
|
@ -3,13 +3,38 @@ import { useSpring, animated } from 'react-spring';
|
|||
import { formatNumber } from '../../lib/format';
|
||||
import styles from './MetricCard.module.css';
|
||||
|
||||
const MetricCard = ({ value = 0, label, format = formatNumber }) => {
|
||||
const MetricCard = ({
|
||||
value = 0,
|
||||
change = 0,
|
||||
label,
|
||||
reverseColors = false,
|
||||
format = formatNumber,
|
||||
}) => {
|
||||
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
|
||||
const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } });
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<animated.div className={styles.value}>{props.x.interpolate(x => format(x))}</animated.div>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.label}>
|
||||
{label}
|
||||
{~~change === 0 && <span className={styles.change}>{format(0)}</span>}
|
||||
{~~change !== 0 && (
|
||||
<animated.span
|
||||
className={`${styles.change} ${
|
||||
change >= 0
|
||||
? !reverseColors
|
||||
? styles.positive
|
||||
: styles.negative
|
||||
: !reverseColors
|
||||
? styles.negative
|
||||
: styles.positive
|
||||
}`}
|
||||
>
|
||||
{changeProps.x.interpolate(x => `${change >= 0 ? '+' : ''}${format(x)}`)}
|
||||
</animated.span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,4 +16,24 @@
|
|||
.label {
|
||||
font-size: var(--font-size-normal);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.change {
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
margin-left: 4px;
|
||||
border: 1px solid var(--gray200);
|
||||
color: var(--gray500);
|
||||
}
|
||||
|
||||
.change.positive {
|
||||
color: var(--green500);
|
||||
}
|
||||
|
||||
.change.negative {
|
||||
color: var(--red500);
|
||||
}
|
||||
|
|
|
@ -34,14 +34,22 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
[url, modified],
|
||||
);
|
||||
|
||||
const formatFunc = format ? formatLongNumber : formatNumber;
|
||||
const formatFunc = format
|
||||
? n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`)
|
||||
: formatNumber;
|
||||
|
||||
function handleSetFormat() {
|
||||
setFormat(state => !state);
|
||||
}
|
||||
|
||||
const { pageviews, uniques, bounces, totaltime } = data || {};
|
||||
const num = Math.min(uniques, bounces);
|
||||
const num = Math.min(data && uniques.value, data && bounces.value);
|
||||
const diffs = data && {
|
||||
pageviews: pageviews.value - pageviews.change,
|
||||
uniques: uniques.value - uniques.change,
|
||||
bounces: bounces.value - bounces.change,
|
||||
totaltime: totaltime.value - totaltime.change,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.bar, className)} onClick={handleSetFormat}>
|
||||
|
@ -51,18 +59,27 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
<>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
value={pageviews}
|
||||
value={pageviews.value}
|
||||
change={pageviews.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
value={uniques}
|
||||
value={uniques.value}
|
||||
change={uniques.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.bounce-rate" defaultMessage="Bounce rate" />}
|
||||
value={uniques ? (num / uniques) * 100 : 0}
|
||||
value={uniques.value ? (num / uniques.value) * 100 : 0}
|
||||
change={
|
||||
uniques.value && uniques.change
|
||||
? (num / uniques.value) * 100 -
|
||||
(Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0
|
||||
: 0
|
||||
}
|
||||
format={n => Number(n).toFixed(0) + '%'}
|
||||
reverseColors
|
||||
/>
|
||||
<MetricCard
|
||||
label={
|
||||
|
@ -71,8 +88,19 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
defaultMessage="Average visit time"
|
||||
/>
|
||||
}
|
||||
value={totaltime && pageviews ? totaltime / (pageviews - bounces) : 0}
|
||||
format={n => formatShortTime(n, ['m', 's'], ' ')}
|
||||
value={
|
||||
totaltime.value && pageviews.value
|
||||
? totaltime.value / (pageviews.value - bounces.value)
|
||||
: 0
|
||||
}
|
||||
change={
|
||||
totaltime.value && pageviews.value
|
||||
? (diffs.totaltime / (diffs.pageviews - diffs.bounces) -
|
||||
totaltime.value / (pageviews.value - bounces.value)) *
|
||||
-1 || 0
|
||||
: 0
|
||||
}
|
||||
format={n => `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -31,7 +31,7 @@ const TYPE_ICONS = {
|
|||
|
||||
export default function RealtimeLog({ data, websites, websiteId }) {
|
||||
const intl = useIntl();
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const [filter, setFilter] = useState(TYPE_ALL);
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ function filterWebsite(data, id) {
|
|||
}
|
||||
|
||||
export default function RealtimeDashboard() {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const [data, setData] = useState();
|
||||
const [websiteId, setWebsiteId] = useState(0);
|
||||
|
|
|
@ -9,7 +9,7 @@ import styles from './DateRangeSetting.module.css';
|
|||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
export default function DateRangeSetting() {
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const [dateRange, setDateRange] = useDateRange();
|
||||
const { startDate, endDate, value } = dateRange;
|
||||
|
||||
|
|
|
@ -1,48 +1,44 @@
|
|||
import React from 'react';
|
||||
import Head from 'next/head';
|
||||
import { menuOptions } from 'lib/lang';
|
||||
import { languages } 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();
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const menuOptions = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
||||
|
||||
function handleSelect(value) {
|
||||
setLocale(value);
|
||||
saveLocale(value);
|
||||
}
|
||||
|
||||
switch (locale) {
|
||||
case 'zh-CN':
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-sc/chinese-simplified-400.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-sc/chinese-simplified-500.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-sc/chinese-simplified-700.css');
|
||||
break;
|
||||
case 'zh-TW':
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-tc/chinese-traditional-400.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-tc/chinese-traditional-500.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-tc/chinese-traditional-700.css');
|
||||
break;
|
||||
case 'ja-JP':
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-jp/japanese-400.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-jp/japanese-500.css');
|
||||
import(/* webpackMode: "eager" */ '@fontsource/noto-sans-jp/japanese-700.css');
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{locale === 'zh-CN' && (
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
)}
|
||||
{locale === 'zh-TW' && (
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
)}
|
||||
{locale === 'ja-JP' && (
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
)}
|
||||
</Head>
|
||||
<MenuButton
|
||||
icon={<Globe />}
|
||||
options={menuOptions}
|
||||
value={locale}
|
||||
menuClassName={styles.menu}
|
||||
renderValue={option => option?.display}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</>
|
||||
<MenuButton
|
||||
icon={<Globe />}
|
||||
options={menuOptions}
|
||||
value={locale}
|
||||
menuClassName={styles.menu}
|
||||
renderValue={option => option?.display}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import Button from 'components/common/Button';
|
|||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Modal from 'components/common/Modal';
|
||||
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
||||
import ResetForm from 'components/forms/ResetForm';
|
||||
import DeleteForm from 'components/forms/DeleteForm';
|
||||
import TrackingCodeForm from 'components/forms/TrackingCodeForm';
|
||||
import ShareUrlForm from 'components/forms/ShareUrlForm';
|
||||
|
@ -16,6 +17,7 @@ import Toast from 'components/common/Toast';
|
|||
import Favicon from 'components/common/Favicon';
|
||||
import Pen from 'assets/pen.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import Reset from 'assets/redo.svg';
|
||||
import Plus from 'assets/plus.svg';
|
||||
import Code from 'assets/code.svg';
|
||||
import LinkIcon from 'assets/link.svg';
|
||||
|
@ -24,6 +26,7 @@ import styles from './WebsiteSettings.module.css';
|
|||
|
||||
export default function WebsiteSettings() {
|
||||
const [editWebsite, setEditWebsite] = useState();
|
||||
const [resetWebsite, setResetWebsite] = useState();
|
||||
const [deleteWebsite, setDeleteWebsite] = useState();
|
||||
const [addWebsite, setAddWebsite] = useState();
|
||||
const [showCode, setShowCode] = useState();
|
||||
|
@ -55,6 +58,9 @@ export default function WebsiteSettings() {
|
|||
<Button icon={<Pen />} size="small" onClick={() => setEditWebsite(row)}>
|
||||
<FormattedMessage id="label.edit" defaultMessage="Edit" />
|
||||
</Button>
|
||||
<Button icon={<Reset />} size="small" onClick={() => setResetWebsite(row)}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteWebsite(row)}>
|
||||
<FormattedMessage id="label.delete" defaultMessage="Delete" />
|
||||
</Button>
|
||||
|
@ -96,6 +102,7 @@ export default function WebsiteSettings() {
|
|||
function handleClose() {
|
||||
setAddWebsite(null);
|
||||
setEditWebsite(null);
|
||||
setResetWebsite(null);
|
||||
setDeleteWebsite(null);
|
||||
setShowCode(null);
|
||||
setShowUrl(null);
|
||||
|
@ -141,6 +148,17 @@ export default function WebsiteSettings() {
|
|||
<WebsiteEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{resetWebsite && (
|
||||
<Modal
|
||||
title={<FormattedMessage id="label.reset-website" defaultMessage="Reset statistics" />}
|
||||
>
|
||||
<ResetForm
|
||||
values={{ type: 'website', id: resetWebsite.website_id, name: resetWebsite.name }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{deleteWebsite && (
|
||||
<Modal
|
||||
title={<FormattedMessage id="label.delete-website" defaultMessage="Delete website" />}
|
||||
|
|
|
@ -11,6 +11,7 @@ services:
|
|||
HASH_SALT: replace-me-with-a-random-string
|
||||
depends_on:
|
||||
- db
|
||||
restart: always
|
||||
db:
|
||||
image: postgres:12-alpine
|
||||
environment:
|
||||
|
@ -20,5 +21,6 @@ services:
|
|||
volumes:
|
||||
- ./sql/schema.postgresql.sql:/docker-entrypoint-initdb.d/schema.postgresql.sql:ro
|
||||
- umami-db-data:/var/lib/postgresql/data
|
||||
restart: always
|
||||
volumes:
|
||||
umami-db-data:
|
||||
|
|
|
@ -9,7 +9,7 @@ import useLocale from './useLocale';
|
|||
|
||||
export default function useDateRange(websiteId, defaultDateRange = DEFAULT_DATE_RANGE) {
|
||||
const dispatch = useDispatch();
|
||||
const [locale] = useLocale();
|
||||
const { locale } = useLocale();
|
||||
const dateRange = useSelector(state => state.websites[websiteId]?.dateRange);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
|
|
|
@ -1,16 +1,49 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { setLocale } from 'redux/actions/app';
|
||||
import { setItem } from 'lib/web';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get, setItem } from 'lib/web';
|
||||
import { LOCALE_CONFIG } from 'lib/constants';
|
||||
import useForceUpdate from 'hooks/useForceUpdate';
|
||||
import enUS from 'public/lang/en-US.json';
|
||||
|
||||
const messages = {
|
||||
'en-US': enUS,
|
||||
};
|
||||
|
||||
export default function useLocale() {
|
||||
const locale = useSelector(state => state.app.locale);
|
||||
const dispatch = useDispatch();
|
||||
const { basePath } = useRouter();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
function saveLocale(value) {
|
||||
setItem(LOCALE_CONFIG, value);
|
||||
dispatch(setLocale(value));
|
||||
async function loadMessages(locale) {
|
||||
const { ok, data } = await get(`${basePath}/lang/${locale}.json`);
|
||||
|
||||
if (ok) {
|
||||
messages[locale] = data;
|
||||
}
|
||||
}
|
||||
|
||||
return [locale, saveLocale];
|
||||
async function saveLocale(value) {
|
||||
if (!messages[value]) {
|
||||
await loadMessages(value);
|
||||
}
|
||||
|
||||
setItem(LOCALE_CONFIG, value);
|
||||
|
||||
if (locale !== value) {
|
||||
dispatch(setLocale(value));
|
||||
} else {
|
||||
forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!messages[locale]) {
|
||||
saveLocale(locale);
|
||||
}
|
||||
}, [locale]);
|
||||
|
||||
return { locale, saveLocale, messages };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"label.accounts": "الحسابات",
|
||||
"label.add-account": "إضافة حساب",
|
||||
"label.add-website": "إضافة موقع",
|
||||
"label.administrator": "مدير عام؟",
|
||||
"label.all": "الكل",
|
||||
"label.all-events": "كافة الأحداث",
|
||||
"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}?",
|
||||
"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": "زائر من {country} يستخدم {browser} على {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": "زوار"
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"label.accounts": "Comptes",
|
||||
"label.add-account": "Afegeix compte",
|
||||
"label.add-website": "Afegeix lloc web",
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Tots",
|
||||
"label.all-events": "Tots els esdeveniments",
|
||||
"label.all-websites": "Tots els llocs web",
|
||||
"label.back": "Enrere",
|
||||
"label.cancel": "Cancel·la",
|
||||
"label.change-password": "Canvia la contrasenya",
|
||||
"label.confirm-password": "Confirma la contrasenya",
|
||||
"label.copy-to-clipboard": "Copia al porta-retalls",
|
||||
"label.current-password": "Contrasenya actual",
|
||||
"label.custom-range": "Rang personalitzat",
|
||||
"label.dashboard": "Panell",
|
||||
"label.date-range": "Interval de dates",
|
||||
"label.default-date-range": "Interval de dates per defecte",
|
||||
"label.delete": "Esborra",
|
||||
"label.delete-account": "Esborra el compte",
|
||||
"label.delete-website": "Esborra el lloc web",
|
||||
"label.dismiss": "Descarta",
|
||||
"label.domain": "Domini",
|
||||
"label.edit": "Edita",
|
||||
"label.edit-account": "Edita el compte",
|
||||
"label.edit-website": "Edita el lloc web",
|
||||
"label.enable-share-url": "Activa l'enllaç per compartir",
|
||||
"label.invalid": "Invàlid",
|
||||
"label.invalid-domain": "Domini invàlid",
|
||||
"label.last-days": "Últims {x} dies",
|
||||
"label.last-hours": "Últimes {x} hores",
|
||||
"label.logged-in-as": "Connectat com {username}",
|
||||
"label.login": "Connecta't",
|
||||
"label.logout": "Desconnecta't",
|
||||
"label.more": "Més",
|
||||
"label.name": "Nom",
|
||||
"label.new-password": "Contrasenya nova",
|
||||
"label.password": "Contrasenya",
|
||||
"label.passwords-dont-match": "Les contrasenyes no coincideixen",
|
||||
"label.profile": "Perfil",
|
||||
"label.realtime": "Temps real",
|
||||
"label.realtime-logs": "Registres a temps real",
|
||||
"label.refresh": "Refresca",
|
||||
"label.required": "Obligatori",
|
||||
"label.reset": "Restableix",
|
||||
"label.save": "Desa",
|
||||
"label.settings": "Configuració",
|
||||
"label.share-url": "Enllaç per compartir",
|
||||
"label.single-day": "Un sol dia",
|
||||
"label.this-month": "Aquest mes",
|
||||
"label.this-week": "Aquesta setmana",
|
||||
"label.this-year": "Aquest any",
|
||||
"label.timezone": "Zona horària",
|
||||
"label.today": "Avui",
|
||||
"label.tracking-code": "Codi de seguiment",
|
||||
"label.unknown": "Desconegut",
|
||||
"label.username": "Nom d'usuari",
|
||||
"label.view-details": "Veure els detalls",
|
||||
"label.websites": "Llocs web",
|
||||
"message.active-users": "{x} {x, plural, one {visitant actual} other {visitants actuals}}",
|
||||
"message.confirm-delete": "Segur que vols esborrar {target}?",
|
||||
"message.copied": "S'ha copiat",
|
||||
"message.delete-warning": "També s'esborraran totes les dades relacionades.",
|
||||
"message.failure": "S'ha produït un error.",
|
||||
"message.get-share-url": "Obté l'enllaç per compartir",
|
||||
"message.get-tracking-code": "Obté el codi de seguiment",
|
||||
"message.go-to-settings": "Vés a la configuració",
|
||||
"message.incorrect-username-password": "Nom d'usuari o contrasenya incorrectes.",
|
||||
"message.log.visitor": "Visitant de {country} usant {browser} a {os} {device}",
|
||||
"message.new-version-available": "Hi ha disponible una nova versió d'umami {version}!",
|
||||
"message.no-data-available": "No hi ha dades disponibles.",
|
||||
"message.no-websites-configured": "No hi ha cap lloc web configurat.",
|
||||
"message.page-not-found": "No s'ha trobat la pàgina.",
|
||||
"message.powered-by": "Funciona amb {name}",
|
||||
"message.save-success": "S'ha desat amb èxit.",
|
||||
"message.share-url": "Aquest és l'enllaç públic per compartir de {target}.",
|
||||
"message.track-stats": "Per seguir les estadístiques de {target}, col·loca el codi següent a la secció {head} del teu lloc web.",
|
||||
"message.type-delete": "Escriu {delete} al quadre següent per confirmar.",
|
||||
"metrics.actions": "Accions",
|
||||
"metrics.average-visit-time": "Temps mitjà de visita",
|
||||
"metrics.bounce-rate": "Percentatge de rebot",
|
||||
"metrics.browsers": "Navegadors",
|
||||
"metrics.countries": "Països",
|
||||
"metrics.device.desktop": "Escriptori",
|
||||
"metrics.device.laptop": "Portàtil",
|
||||
"metrics.device.mobile": "Mòbil",
|
||||
"metrics.device.tablet": "Tauleta",
|
||||
"metrics.devices": "Dispositius",
|
||||
"metrics.events": "Esdeveniments",
|
||||
"metrics.filter.combined": "Combinat",
|
||||
"metrics.filter.domain-only": "Només domini",
|
||||
"metrics.filter.raw": "En cru",
|
||||
"metrics.operating-systems": "Sistemes operatius",
|
||||
"metrics.page-views": "Pàgines vistes",
|
||||
"metrics.pages": "Pàgines",
|
||||
"metrics.referrers": "Referents",
|
||||
"metrics.unique-visitors": "Visitants únics",
|
||||
"metrics.views": "Vistes",
|
||||
"metrics.visitors": "Visitants"
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
"label.delete": "Delete",
|
||||
"label.delete-account": "Delete account",
|
||||
"label.delete-website": "Delete website",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.dismiss": "Dismiss",
|
||||
"label.domain": "Domain",
|
||||
"label.edit": "Edit",
|
||||
|
@ -58,8 +59,10 @@
|
|||
"label.view-details": "View details",
|
||||
"label.websites": "Websites",
|
||||
"message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.confirm-delete": "Are your sure you want to delete {target}?",
|
||||
"message.copied": "Copied!",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.delete-warning": "All associated data will be deleted as well.",
|
||||
"message.failure": "Something went wrong.",
|
||||
"message.get-share-url": "Get share URL",
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
{
|
||||
"label.accounts": "حساب ها",
|
||||
"label.accounts": "حسابها",
|
||||
"label.add-account": "افزودن حساب",
|
||||
"label.add-website": "افزودن وب سایت",
|
||||
"label.add-website": "افزودن وبسایت",
|
||||
"label.administrator": "مدیر",
|
||||
"label.all": "همه",
|
||||
"label.all-websites": "همه وب سایت ها",
|
||||
"label.all-events": "همه رویداد ها",
|
||||
"label.all-websites": "همهی وبسایتها",
|
||||
"label.all-events": "همهی رویدادها",
|
||||
"label.back": "برگشت",
|
||||
"label.cancel": "انصراف",
|
||||
"label.change-password": "تغییر رمز",
|
||||
"label.confirm-password": "تایید رمز",
|
||||
"label.copy-to-clipboard": "کپی به حافظه",
|
||||
"label.current-password": "رمز فعلی",
|
||||
"label.custom-range": "محدوده دلخواه",
|
||||
"label.custom-range": "محدودهی دلخواه",
|
||||
"label.dashboard": "داشبورد",
|
||||
"label.date-range": "محدوده تاریخ",
|
||||
"label.default-date-range": "محدوده پیشفرض تاریخ",
|
||||
"label.date-range": "محدودهی تاریخ",
|
||||
"label.default-date-range": "محدودهی پیشفرض تاریخ",
|
||||
"label.delete": "حذف",
|
||||
"label.delete-account": "حذف حساب",
|
||||
"label.delete-website": "حذف وب سایت",
|
||||
"label.delete-website": "حذف وبسایت",
|
||||
"label.dismiss": "رد کردن",
|
||||
"label.domain": "دامنه",
|
||||
"label.edit": "ویرایش",
|
||||
"label.edit-account": "ویرایش حساب",
|
||||
"label.edit-website": "ویرایش وب سایت",
|
||||
"label.edit-website": "ویرایش وبسایت",
|
||||
"label.enable-share-url": "فعال کردن اشتراک گذاری URL",
|
||||
"label.invalid": "نامعتبر",
|
||||
"label.invalid-domain": "دامنه نامعتبر",
|
||||
"label.invalid-domain": "دامنهی نامعتبر",
|
||||
"label.last-days": "لیست {x} روز",
|
||||
"label.last-hours": "لیست {x} ساعت",
|
||||
"label.logged-in-as": "وارد شده به عنوان {username}",
|
||||
|
@ -36,13 +36,13 @@
|
|||
"label.name": "نام",
|
||||
"label.new-password": "رمز جدید",
|
||||
"label.password": "رمز",
|
||||
"label.passwords-dont-match": "رمز ها یکسان نیستند",
|
||||
"label.passwords-dont-match": "رمزها یکسان نیستند",
|
||||
"label.profile": "پروفایل",
|
||||
"label.realtime": "آمار هم اکنون",
|
||||
"label.realtime-logs": "لاگ های هم اکنون",
|
||||
"label.refresh": "تازه کردن",
|
||||
"label.required": "لازم",
|
||||
"label.reset": "ریست",
|
||||
"label.realtime": "آمار زنده",
|
||||
"label.realtime-logs": "لاگهای زنده",
|
||||
"label.refresh": "بهروزرسانی",
|
||||
"label.required": "ضروری",
|
||||
"label.reset": "بازنشانی",
|
||||
"label.save": "ذخیره",
|
||||
"label.settings": "تنظیمات",
|
||||
"label.share-url": "به اشتراک گذاری URL",
|
||||
|
@ -50,51 +50,51 @@
|
|||
"label.this-month": "این ماه",
|
||||
"label.this-week": "این هفته",
|
||||
"label.this-year": "امسال",
|
||||
"label.timezone": "منطقه زمانی",
|
||||
"label.timezone": "منطقهی زمانی",
|
||||
"label.today": "امروز",
|
||||
"label.tracking-code": "کد رهگیری",
|
||||
"label.unknown": "ناشناخته",
|
||||
"label.username": "نام کاربری",
|
||||
"label.view-details": "مشاهده جزئیات",
|
||||
"label.websites": "وب سایت ها",
|
||||
"label.view-details": "مشاهدهی جزئیات",
|
||||
"label.websites": "وبسایتها",
|
||||
"message.active-users": "{x} هم اکنون {x, plural, one {یک} other {از میان}}",
|
||||
"message.confirm-delete": "آیا مطمئن هستید می خواهید {target} را حذف کنید?",
|
||||
"message.confirm-delete": "آیا مطمئن هستید میخواهید {target} را حذف کنید?",
|
||||
"message.copied": "کپی شد!",
|
||||
"message.delete-warning": "همه داده های مرتبط هم حذف خواهد شد.",
|
||||
"message.delete-warning": "همهی دادههای مرتبط هم حذف خواهد شد.",
|
||||
"message.failure": "مشکلی پیش آمده است.",
|
||||
"message.get-share-url": "دریافت URL برای اشتراک گذاری",
|
||||
"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.log.visitor": "بازدیدکننده از کشور {country} با مروگر {browser} در {os} {device}",
|
||||
"message.new-version-available": "نسخهی جدید umami ({version}) موجود است!",
|
||||
"message.no-data-available": "اطلاعاتی موجود نیست.",
|
||||
"message.no-websites-configured": "شما هیچ وب سایتی را پیکر بندی نکرده اید.",
|
||||
"message.no-websites-configured": "شما هیچ وبسایتی را پیکربندی نکردهاید.",
|
||||
"message.page-not-found": "صفحه یافت نشد.",
|
||||
"message.powered-by": "قدرت گرفته توسط {name}",
|
||||
"message.save-success": "با موفقیت ذخیره شد.",
|
||||
"message.share-url": "این URL به اشتراک گذاشته شده عمومی برای {target} است.",
|
||||
"message.track-stats": "برای ردیابی آمار {target}, کد روبرو را در قسمت {head} وب سایت قرار دهید.",
|
||||
"message.track-stats": "برای ردیابی آمار {target}, کد روبرو را در قسمت {head} وبسایت قرار دهید.",
|
||||
"message.type-delete": "جهت اطمینان '{delete}' را در کادر زیر بنویسید.",
|
||||
"metrics.actions": "اقدامات",
|
||||
"metrics.average-visit-time": "میانگین زمان بازدید",
|
||||
"metrics.bounce-rate": "نرخ Bounce",
|
||||
"metrics.browsers": "مروگر ها",
|
||||
"metrics.countries": "کشور ها",
|
||||
"metrics.browsers": "مروگرها",
|
||||
"metrics.countries": "کشورها",
|
||||
"metrics.device.desktop": "دسکتاپ",
|
||||
"metrics.device.laptop": "لپ تاپ",
|
||||
"metrics.device.laptop": "لپتاپ",
|
||||
"metrics.device.mobile": "موبایل",
|
||||
"metrics.device.tablet": "تبلت",
|
||||
"metrics.devices": "دستگاه ها",
|
||||
"metrics.events": "رویداد ها",
|
||||
"metrics.devices": "دستگاهها",
|
||||
"metrics.events": "رویدادها",
|
||||
"metrics.filter.combined": "ترکیب شده",
|
||||
"metrics.filter.domain-only": "فقط دامنه",
|
||||
"metrics.filter.raw": "خام",
|
||||
"metrics.operating-systems": "سیستم عامل ها",
|
||||
"metrics.operating-systems": "سیستمعاملها",
|
||||
"metrics.page-views": "بازدید صفحه",
|
||||
"metrics.pages": "صفحه ها",
|
||||
"metrics.pages": "صفحهها",
|
||||
"metrics.referrers": "ارجاع دهندگان",
|
||||
"metrics.unique-visitors": "بازدید کننده خالص",
|
||||
"metrics.unique-visitors": "بازدیدکنندههای یکتا",
|
||||
"metrics.views": "بازدید",
|
||||
"metrics.visitors": "بازدید کننده"
|
||||
"metrics.visitors": "بازدیدکننده"
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"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.active-users": "{x} nykyinen {x, plural, one {yksi} other {muut}}",
|
||||
"message.confirm-delete": "Haluatko varmasti poistaa {target}?",
|
||||
"message.copied": "Kopioitu!",
|
||||
"message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.",
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"label.accounts": "Fiókok",
|
||||
"label.add-account": "Fiók hozzáadása",
|
||||
"label.add-website": "Weboldal hozzáadása",
|
||||
"label.administrator": "Adminisztrátor",
|
||||
"label.all": "Összes",
|
||||
"label.all-websites": "Összes weboldal",
|
||||
"label.all-events": "Összes esemény",
|
||||
"label.back": "Vissza",
|
||||
"label.cancel": "Mégsem",
|
||||
"label.change-password": "Jelszó módosítása",
|
||||
"label.confirm-password": "Jelszó megerősítése",
|
||||
"label.copy-to-clipboard": "Vágólapra másolás",
|
||||
"label.current-password": "Jelenlegi jelszó",
|
||||
"label.custom-range": "Egyedi tartomány",
|
||||
"label.dashboard": "Áttekintés",
|
||||
"label.date-range": "Időintervallum",
|
||||
"label.default-date-range": "Alapértelmezett időintervallum",
|
||||
"label.delete": "Eltávolítás",
|
||||
"label.delete-account": "Fiók eltávolítása",
|
||||
"label.delete-website": "Weboldal eltávolítása",
|
||||
"label.dismiss": "Mellőzés",
|
||||
"label.domain": "Domain",
|
||||
"label.edit": "Módosítás",
|
||||
"label.edit-account": "Fiók módosítása",
|
||||
"label.edit-website": "Weboldal módosítása",
|
||||
"label.enable-share-url": "URL-megosztás engedélyezése",
|
||||
"label.invalid": "Érvénytelen",
|
||||
"label.invalid-domain": "Érvénytelen domain",
|
||||
"label.last-days": "Legutóbbi {x} nap",
|
||||
"label.last-hours": "Legutóbbi {x} óra",
|
||||
"label.logged-in-as": "Bejelentkezve, mint {username}",
|
||||
"label.login": "Bejelentkezés",
|
||||
"label.logout": "Kijelentkezés",
|
||||
"label.more": "Bővebben",
|
||||
"label.name": "Név",
|
||||
"label.new-password": "Új jelszó",
|
||||
"label.password": "Jelszó",
|
||||
"label.passwords-dont-match": "A jelszavak nem egyeznek",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Valós idejű",
|
||||
"label.realtime-logs": "Valós idejű napló",
|
||||
"label.refresh": "Frissítés",
|
||||
"label.required": "Kötelező",
|
||||
"label.reset": "Visszaállítás",
|
||||
"label.save": "Mentés",
|
||||
"label.settings": "Beállítások",
|
||||
"label.share-url": "URL megosztása",
|
||||
"label.single-day": "Egy nap",
|
||||
"label.this-month": "Ezen hónap",
|
||||
"label.this-week": "Ezen hét",
|
||||
"label.this-year": "Ezen év",
|
||||
"label.timezone": "Időzóna",
|
||||
"label.today": "Ma",
|
||||
"label.tracking-code": "Követési kód",
|
||||
"label.unknown": "Ismeretlen",
|
||||
"label.username": "Felhasználónév",
|
||||
"label.view-details": "Részletek",
|
||||
"label.websites": "Weboldalak",
|
||||
"message.active-users": "{x} {x, plural, one {látogató} other {latógató}} jelenleg",
|
||||
"message.confirm-delete": "Biztos, hogy törölni szeretnéd {target} elemet?",
|
||||
"message.copied": "Kimásolva!",
|
||||
"message.delete-warning": "Minden társított adat törlésre kerül.",
|
||||
"message.failure": "Valami baj történt.",
|
||||
"message.get-share-url": "Megosztási URL kimásolása",
|
||||
"message.get-tracking-code": "Követési kód kimásolása",
|
||||
"message.go-to-settings": "Tovább a beállításokhoz",
|
||||
"message.incorrect-username-password": "Érvénytelen felhasználónév/jelszó.",
|
||||
"message.log.visitor": "Látógató {country} területéről, {os} {device} eszközön, {browser} böngészőből.",
|
||||
"message.new-version-available": "Elérhető az umami {version} új verziója!",
|
||||
"message.no-data-available": "Nincs rendelkezésre álló adat.",
|
||||
"message.no-websites-configured": "Még nem állítottál be egyetlen weboldalt sem.",
|
||||
"message.page-not-found": "Oldal nem található.",
|
||||
"message.powered-by": "Működteti az {name}",
|
||||
"message.save-success": "Sikeres mentés.",
|
||||
"message.share-url": "{target} nyilvánosan megosztott URL címe.",
|
||||
"message.track-stats": "{target} statisztikáinak nyomon követéséhez, helyezd el az alábbi kódot a weboldalad {head} részébe.",
|
||||
"message.type-delete": "Megerősítéshez írd be az alábbi mezőbe azt, hogy {delete}.",
|
||||
"metrics.actions": "Műveletek",
|
||||
"metrics.average-visit-time": "Átlagos látogatási idő",
|
||||
"metrics.bounce-rate": "Visszafordulási arány",
|
||||
"metrics.browsers": "Böngészők",
|
||||
"metrics.countries": "Országok",
|
||||
"metrics.device.desktop": "Asztali számítógép",
|
||||
"metrics.device.laptop": "Laptop",
|
||||
"metrics.device.mobile": "Telefon",
|
||||
"metrics.device.tablet": "Táblagép",
|
||||
"metrics.devices": "Eszközök",
|
||||
"metrics.events": "Események",
|
||||
"metrics.filter.combined": "Összevont",
|
||||
"metrics.filter.domain-only": "Csak domain",
|
||||
"metrics.filter.raw": "Nyers",
|
||||
"metrics.operating-systems": "Operációs rendszerek",
|
||||
"metrics.page-views": "Oldalmegtekintések",
|
||||
"metrics.pages": "Oldalak",
|
||||
"metrics.referrers": "Hivatkozók",
|
||||
"metrics.unique-visitors": "Egyedi látogatók",
|
||||
"metrics.views": "Megtekintések",
|
||||
"metrics.visitors": "Látogatók"
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"label.accounts": "계정",
|
||||
"label.add-account": "계정 추가",
|
||||
"label.add-website": "웹사이트 추가",
|
||||
"label.administrator": "관리자",
|
||||
"label.all": "전체",
|
||||
"label.all-websites": "모든 웹사이트",
|
||||
"label.all-events": "모든 이벤트",
|
||||
"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": "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": "공유 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": "공유 URL 가져오기",
|
||||
"message.get-tracking-code": "추적 코드 가져오기",
|
||||
"message.go-to-settings": "설정으로 이동",
|
||||
"message.incorrect-username-password": "사용자 이름/비밀번호가 잘못되었습니다..",
|
||||
"message.log.visitor": "{os} {device}에서 {browser}을(를) 사용하는 {country}의 방문자",
|
||||
"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}의 공개적으로 공유된 URL입니다.",
|
||||
"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": "페이지 뷰(PV)",
|
||||
"metrics.pages": "페이지",
|
||||
"metrics.referrers": "리퍼러",
|
||||
"metrics.unique-visitors": "순방문자(UV)",
|
||||
"metrics.views": "조회수",
|
||||
"metrics.visitors": "방문객"
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"label.administrator": "Админ",
|
||||
"label.all": "Бүх",
|
||||
"label.all-websites": "Бүх вебүүд",
|
||||
"label.all-events": "Бүх үйл явдал",
|
||||
"label.back": "Буцах",
|
||||
"label.cancel": "Цуцлах",
|
||||
"label.change-password": "Нууц үг солих",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"label.administrator": "Administrator",
|
||||
"label.all": "Wszystkie",
|
||||
"label.all-websites": "Wszystkie witryny",
|
||||
"label.all-events": "Wszystkie wydarzenia",
|
||||
"label.back": "Powrót",
|
||||
"label.cancel": "Anuluj",
|
||||
"label.change-password": "Zmień hasło",
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"label.accounts": "Računi",
|
||||
"label.add-account": "Dodaj račun",
|
||||
"label.add-website": "Dodaj spletno mesto",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all": "Vse",
|
||||
"label.all-websites": "Vsa spletna mesta",
|
||||
"label.all-events": "Vsi dogodki",
|
||||
"label.back": "Nazaj",
|
||||
"label.cancel": "Prekliči",
|
||||
"label.change-password": "Zamenjaj geslo",
|
||||
"label.confirm-password": "Potrditev gesla",
|
||||
"label.copy-to-clipboard": "Kopiraj v odložišče",
|
||||
"label.current-password": "Trenutno geslo",
|
||||
"label.custom-range": "Razpon po meri",
|
||||
"label.dashboard": "Nadzorna plošča",
|
||||
"label.date-range": "Časovni razpon",
|
||||
"label.default-date-range": "Privzeti časovni razpon",
|
||||
"label.delete": "Izbriši",
|
||||
"label.delete-account": "Izbriši račun",
|
||||
"label.delete-website": "Izbriši spletno mesto",
|
||||
"label.dismiss": "Opusti",
|
||||
"label.domain": "Domena",
|
||||
"label.edit": "Uredi",
|
||||
"label.edit-account": "Uredi račun",
|
||||
"label.edit-website": "Uredi spletno stran",
|
||||
"label.enable-share-url": "Omogoči URL za skupno rabo",
|
||||
"label.invalid": "Neveljavno",
|
||||
"label.invalid-domain": "Neveljavna domena",
|
||||
"label.last-days": "Zadnjih {x} dni",
|
||||
"label.last-hours": "Zadnjih {x} ur",
|
||||
"label.logged-in-as": "Prijavljen kot {username}",
|
||||
"label.login": "Prijava",
|
||||
"label.logout": "Odjava",
|
||||
"label.more": "Več",
|
||||
"label.name": "Ime",
|
||||
"label.new-password": "Novo geslo",
|
||||
"label.password": "Geslo",
|
||||
"label.passwords-dont-match": "Gesli se ne ujemata",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "V realnem času",
|
||||
"label.realtime-logs": "Dnevnik v realnem času",
|
||||
"label.refresh": "Osveži",
|
||||
"label.required": "Zahtevano",
|
||||
"label.reset": "Ponastavi",
|
||||
"label.save": "Shrani",
|
||||
"label.settings": "Nastavitve",
|
||||
"label.share-url": "Deli URL",
|
||||
"label.single-day": "En dan",
|
||||
"label.this-month": "Ta mesec",
|
||||
"label.this-week": "Ta teden",
|
||||
"label.this-year": "Letos",
|
||||
"label.timezone": "Časovni pas",
|
||||
"label.today": "Danes",
|
||||
"label.tracking-code": "Koda za sledenje",
|
||||
"label.unknown": "Neznano",
|
||||
"label.username": "Uporabniško ime",
|
||||
"label.view-details": "Prikaži podrobnosti",
|
||||
"label.websites": "Spletna mesta",
|
||||
"message.active-users": "{x} trenutni {x, plural, one {obiskovalec} other {obiskovalcev}}",
|
||||
"message.confirm-delete": "Ste prepričani, da želite izbrisati {target}?",
|
||||
"message.copied": "Kopirano!",
|
||||
"message.delete-warning": "Izbrisani bodo tudi vsi povezani podatki.",
|
||||
"message.failure": "Prišlo je do napake.",
|
||||
"message.get-share-url": "Pridobi URL za skupno rabo",
|
||||
"message.get-tracking-code": "Pridobi kodo za sledenje",
|
||||
"message.go-to-settings": "Pojdi v nastavitve",
|
||||
"message.incorrect-username-password": "Nepravilno uporabniško ime/geslo",
|
||||
"message.log.visitor": "Obiskovalec iz {country} uporablja {browser} na {os} {device}",
|
||||
"message.new-version-available": "Nova verzija umami {version} je na voljo!",
|
||||
"message.no-data-available": "Podatki niso na voljo.",
|
||||
"message.no-websites-configured": "Ni nastavljenih spletnih mest.",
|
||||
"message.page-not-found": "Stran ni bila najdena.",
|
||||
"message.powered-by": "Zagotavlja {name}",
|
||||
"message.save-success": "Uspešno shranjeno.",
|
||||
"message.share-url": "To je javno dostopen naslov URL za {target}.",
|
||||
"message.track-stats": "Če želite spremljati statistične podatke za {target}, v {head} del vašega spletnega mesta namestite naslednjo kodo.",
|
||||
"message.type-delete": "V spodnje polje vnesite {delete} za potrditev.",
|
||||
"metrics.actions": "Dejanja",
|
||||
"metrics.average-visit-time": "Povprečni čas obiska",
|
||||
"metrics.bounce-rate": "Zapustna stopnja",
|
||||
"metrics.browsers": "Brskalniki",
|
||||
"metrics.countries": "Države",
|
||||
"metrics.device.desktop": "Namizni računalnik",
|
||||
"metrics.device.laptop": "Prenosni računalnik",
|
||||
"metrics.device.mobile": "Mobilni telefon",
|
||||
"metrics.device.tablet": "Tablični računalnik",
|
||||
"metrics.devices": "Naprave",
|
||||
"metrics.events": "Dogodki",
|
||||
"metrics.filter.combined": "Skupno",
|
||||
"metrics.filter.domain-only": "Samo domena",
|
||||
"metrics.filter.raw": "Neobdelane meritve",
|
||||
"metrics.operating-systems": "Operacijski sistemi",
|
||||
"metrics.page-views": "Ogledi strani",
|
||||
"metrics.pages": "Strani",
|
||||
"metrics.referrers": "Viri",
|
||||
"metrics.unique-visitors": "Unikatni obiskovalci",
|
||||
"metrics.views": "Ogledi",
|
||||
"metrics.visitors": "Obiskovalci"
|
||||
}
|
|
@ -79,7 +79,7 @@
|
|||
"metrics.average-visit-time": "平均访问时间",
|
||||
"metrics.bounce-rate": "跳出率",
|
||||
"metrics.browsers": "浏览器",
|
||||
"metrics.countries": "国家",
|
||||
"metrics.countries": "国家/地区",
|
||||
"metrics.device.desktop": "桌面电脑",
|
||||
"metrics.device.laptop": "笔记本",
|
||||
"metrics.device.mobile": "手机",
|
||||
|
|
|
@ -57,29 +57,29 @@
|
|||
"label.view-details": "查看更多",
|
||||
"label.websites": "網站",
|
||||
"message.active-users": "当前線上 {x} 人",
|
||||
"message.confirm-delete": "你確定要删除{target}嗎?",
|
||||
"message.copied": "複製成功!",
|
||||
"message.delete-warning": "所有相關數據將會被删除.",
|
||||
"message.failure": "出現錯誤.",
|
||||
"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": "自 {country} 的訪客在搭載 {os} 的 {device} 上使用 {browser} 進行訪問.",
|
||||
"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.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": "將以下代碼放入被設定網站的{head}部分来收集{target}的資料.",
|
||||
"message.type-delete": "在下方空格輸入{delete}確認",
|
||||
"message.save-success": "成功保存。",
|
||||
"message.share-url": "這是 {target} 的分享連結。",
|
||||
"message.track-stats": "將以下代碼放入被設定網站的 {head} 部分来收集 {target} 的資料。",
|
||||
"message.type-delete": "在下方空格輸入 {delete} 確認",
|
||||
"metrics.actions": "用戶行為",
|
||||
"metrics.average-visit-time": "平均訪問時間",
|
||||
"metrics.bounce-rate": "跳出率",
|
||||
"metrics.browsers": "瀏覽器",
|
||||
"metrics.countries": "國家",
|
||||
"metrics.countries": "國家/地區",
|
||||
"metrics.device.desktop": "桌機",
|
||||
"metrics.device.laptop": "筆記本",
|
||||
"metrics.device.mobile": "手機",
|
||||
|
@ -94,6 +94,6 @@
|
|||
"metrics.pages": "網頁",
|
||||
"metrics.referrers": "指入域名",
|
||||
"metrics.unique-visitors": "獨立訪客",
|
||||
"metrics.views": "页面流量",
|
||||
"metrics.views": "頁面流量",
|
||||
"metrics.visitors": "獨立訪客"
|
||||
}
|
|
@ -39,11 +39,11 @@ export function getRandomChars(n) {
|
|||
return s;
|
||||
}
|
||||
|
||||
export async function hashPassword(password) {
|
||||
export function hashPassword(password) {
|
||||
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export async function checkPassword(password, hash) {
|
||||
export function checkPassword(password, hash) {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { removeTrailingSlash, removeWWW, getDomainName } from './url';
|
|||
|
||||
export const urlFilter = (data, { raw }) => {
|
||||
const isValidUrl = url => {
|
||||
return url !== '' && !url.startsWith('#');
|
||||
return url !== '' && url !== null && !url.startsWith('#');
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
|
@ -51,7 +51,7 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
|
|||
const links = {};
|
||||
|
||||
const isValidRef = ref => {
|
||||
return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#');
|
||||
return ref !== '' && ref !== null && !ref.startsWith('/') && !ref.startsWith('#');
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
|
|
149
lib/lang.js
149
lib/lang.js
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
arSA,
|
||||
cs,
|
||||
sk,
|
||||
da,
|
||||
|
@ -14,6 +15,8 @@ import {
|
|||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
mn,
|
||||
ms,
|
||||
nb,
|
||||
nl,
|
||||
|
@ -22,80 +25,60 @@ import {
|
|||
ptBR,
|
||||
ro,
|
||||
ru,
|
||||
sl,
|
||||
sv,
|
||||
ta,
|
||||
tr,
|
||||
uk,
|
||||
zhCN,
|
||||
zhTW,
|
||||
ca,
|
||||
hu,
|
||||
} 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';
|
||||
import zhTWMessages from 'lang-compiled/zh-TW.json';
|
||||
import trTRMessages from 'lang-compiled/tr-TR.json';
|
||||
import ruRUMessages from 'lang-compiled/ru-RU.json';
|
||||
import deDEMessages from 'lang-compiled/de-DE.json';
|
||||
import jaMessages from 'lang-compiled/ja-JP.json';
|
||||
import esMXMessages from 'lang-compiled/es-MX.json';
|
||||
import frMessages from 'lang-compiled/fr-FR.json';
|
||||
import mnMNMessages from 'lang-compiled/mn-MN.json';
|
||||
import daMessages from 'lang-compiled/da-DK.json';
|
||||
import svMessages from 'lang-compiled/sv-SE.json';
|
||||
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 ptBRMessages from 'lang-compiled/pt-BR.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';
|
||||
import csMessages from 'lang-compiled/cs-CZ.json';
|
||||
import skMessages from 'lang-compiled/sk-SK.json';
|
||||
import plMessages from 'lang-compiled/pl-PL.json';
|
||||
import taMessages from 'lang-compiled/ta-IN.json';
|
||||
import hiMessages from 'lang-compiled/hi-IN.json';
|
||||
import heMessages from 'lang-compiled/he-IL.json';
|
||||
import itMessages from 'lang-compiled/it-IT.json';
|
||||
import faIRMessages from 'lang-compiled/fa-IR.json';
|
||||
import msMYMessages from 'lang-compiled/ms-MY.json';
|
||||
|
||||
export const messages = {
|
||||
'en-US': enMessages,
|
||||
'nl-NL': nlMessages,
|
||||
'zh-CN': zhCNMessages,
|
||||
'zh-TW': zhTWMessages,
|
||||
'de-DE': deDEMessages,
|
||||
'ru-RU': ruRUMessages,
|
||||
'tr-TR': trTRMessages,
|
||||
'ja-JP': jaMessages,
|
||||
'es-MX': esMXMessages,
|
||||
'fr-FR': frMessages,
|
||||
'mn-MN': mnMNMessages,
|
||||
'da-DK': daMessages,
|
||||
'sv-SE': svMessages,
|
||||
'el-GR': grMessages,
|
||||
'fo-FO': foMessages,
|
||||
'pt-PT': ptMessages,
|
||||
'pt-BR': ptBRMessages,
|
||||
'ro-RO': roMessages,
|
||||
'nb-NO': nbNOMessages,
|
||||
'id-ID': idMessages,
|
||||
'uk-UA': ukMessages,
|
||||
'fi-FI': fiMessages,
|
||||
'cs-CZ': csMessages,
|
||||
'sk-SK': skMessages,
|
||||
'pl-PL': plMessages,
|
||||
'ta-IN': taMessages,
|
||||
'hi-IN': hiMessages,
|
||||
'he-IL': heMessages,
|
||||
'it-IT': itMessages,
|
||||
'fa-IR': faIRMessages,
|
||||
'ms-MY': msMYMessages,
|
||||
export const languages = {
|
||||
'ar-SA': { label: 'العربية', display: 'ar' },
|
||||
'zh-CN': { label: '中文', display: 'cn' },
|
||||
'zh-TW': { label: '中文(繁體)', display: 'tw' },
|
||||
'ca-ES': { label: 'Català', display: 'ca' },
|
||||
'cs-CZ': { label: 'Čeština', display: 'cs' },
|
||||
'da-DK': { label: 'Dansk', display: 'da' },
|
||||
'de-DE': { label: 'Deutsch', display: 'de' },
|
||||
'en-US': { label: 'English', display: 'en' },
|
||||
'es-MX': { label: 'Español', display: 'es' },
|
||||
'fa-IR': { label: 'فارسی', display: 'fa' },
|
||||
'fo-FO': { label: 'Føroyskt', display: 'fo' },
|
||||
'fr-FR': { label: 'Français', display: 'fr' },
|
||||
'el-GR': { label: 'Ελληνικά', display: 'el' },
|
||||
'he-IL': { label: 'עברית', display: 'he' },
|
||||
'hi-IN': { label: 'हिन्दी', display: 'hi' },
|
||||
'hu-HU': { label: 'Hungarian', display: 'hu' },
|
||||
'it-IT': { label: 'Italiano', display: 'it' },
|
||||
'id-ID': { label: 'Bahasa Indonesia', display: 'id' },
|
||||
'ja-JP': { label: '日本語', display: 'ja' },
|
||||
'ko-KR': { label: '한국어', display: 'ko' },
|
||||
'ms-MY': { label: 'Malay', display: 'ms' },
|
||||
'mn-MN': { label: 'Монгол', display: 'mn' },
|
||||
'nl-NL': { label: 'Nederlands', display: 'nl' },
|
||||
'nb-NO': { label: 'Norsk Bokmål', display: 'nb' },
|
||||
'pl-PL': { label: 'Polski', display: 'pl' },
|
||||
'pt-PT': { label: 'Português', display: 'pt' },
|
||||
'pt-BR': { label: 'Português do Brasil', display: 'pt-BR' },
|
||||
'ru-RU': { label: 'Русский', display: 'ru' },
|
||||
'ro-RO': { label: 'Română', display: 'ro' },
|
||||
'sk-SK': { label: 'Slovenčina', display: 'sk' },
|
||||
'sl-SI': { label: 'Slovene', display: 'sl' },
|
||||
'fi-FI': { label: 'Suomi', display: 'fi' },
|
||||
'sv-SE': { label: 'Svenska', display: 'sv' },
|
||||
'ta-IN': { label: 'தமிழ்', display: 'ta' },
|
||||
'tr-TR': { label: 'Türkçe', display: 'tr' },
|
||||
'uk-UA': { label: 'українська', display: 'uk' },
|
||||
};
|
||||
|
||||
export const rtlLocales = ['ar-SA', 'fa-IR'];
|
||||
|
||||
export const dateLocales = {
|
||||
'ar-SA': arSA,
|
||||
'en-US': enUS,
|
||||
'nl-NL': nl,
|
||||
'zh-CN': zhCN,
|
||||
|
@ -108,7 +91,7 @@ export const dateLocales = {
|
|||
'ja-JP': ja,
|
||||
'es-MX': es,
|
||||
'fr-FR': fr,
|
||||
'mn-MN': enUS,
|
||||
'mn-MN': mn,
|
||||
'el-GR': el,
|
||||
'fo-FO': da,
|
||||
'pt-PT': pt,
|
||||
|
@ -127,38 +110,8 @@ export const dateLocales = {
|
|||
'it-IT': it,
|
||||
'fa-IR': faIR,
|
||||
'ms-MY': ms,
|
||||
'ca-ES': ca,
|
||||
'hu-HU': hu,
|
||||
'ko-KR': ko,
|
||||
'sl-SI': sl,
|
||||
};
|
||||
|
||||
export const menuOptions = [
|
||||
{ label: '中文', value: 'zh-CN', display: 'cn' },
|
||||
{ label: '中文(繁體)', value: 'zh-TW', display: 'tw' },
|
||||
{ label: 'Čeština', value: 'cs-CZ', display: 'cs' },
|
||||
{ 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: 'فارسی', value: 'fa-IR', display: 'fa' },
|
||||
{ label: 'Føroyskt', value: 'fo-FO', display: 'fo' },
|
||||
{ label: 'Français', value: 'fr-FR', display: 'fr' },
|
||||
{ label: 'Ελληνικά', value: 'el-GR', display: 'el' },
|
||||
{ label: 'עברית', value: 'he-IL', display: 'he' },
|
||||
{ label: 'हिन्दी', value: 'hi-IN', display: 'hi' },
|
||||
{ label: 'Italiano', value: 'it-IT', display: 'it' },
|
||||
{ label: 'Bahasa Indonesia', value: 'id-ID', display: 'id' },
|
||||
{ label: '日本語', value: 'ja-JP', display: 'ja' },
|
||||
{ label: 'Malay', value: 'ms-MY', display: 'ms' },
|
||||
{ label: 'Монгол', value: 'mn-MN', display: 'mn' },
|
||||
{ label: 'Nederlands', value: 'nl-NL', display: 'nl' },
|
||||
{ label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' },
|
||||
{ label: 'Polski', value: 'pl-PL', display: 'pl' },
|
||||
{ label: 'Português', value: 'pt-PT', display: 'pt' },
|
||||
{ label: 'Português do Brasil', value: 'pt-BR', display: 'pt-BR' },
|
||||
{ label: 'Русский', value: 'ru-RU', display: 'ru' },
|
||||
{ label: 'Română', value: 'ro-RO', display: 'ro' },
|
||||
{ label: 'Slovenčina', value: 'sk-SK', display: 'sk' },
|
||||
{ label: 'Suomi', value: 'fi-FI', display: 'fi' },
|
||||
{ label: 'Svenska', value: 'sv-SE', display: 'sv' },
|
||||
{ label: 'தமிழ்', value: 'ta-IN', display: 'ta' },
|
||||
{ label: 'Türkçe', value: 'tr-TR', display: 'tr' },
|
||||
{ label: 'українська', value: 'uk-UA', display: 'uk' },
|
||||
];
|
||||
|
|
|
@ -141,6 +141,10 @@ export async function updateWebsite(website_id, data) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function resetWebsite(website_id) {
|
||||
return runQuery(prisma.$queryRaw`delete from session where website_id=${website_id}`);
|
||||
}
|
||||
|
||||
export async function deleteWebsite(website_id) {
|
||||
return runQuery(
|
||||
/* Prisma bug, does not cascade on non-nullable foreign keys
|
||||
|
|
|
@ -10,9 +10,7 @@ module.exports = {
|
|||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
issuer: {
|
||||
test: /\.js$/,
|
||||
},
|
||||
issuer: /\.js$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
|
|
53
package.json
53
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "umami",
|
||||
"version": "1.17.0",
|
||||
"version": "1.22.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"extract-lang": "formatjs extract '{pages,components}/**/*.js' --out-file build/messages.json",
|
||||
"merge-lang": "node scripts/merge-lang.js",
|
||||
"format-lang": "node scripts/format-lang.js",
|
||||
"compile-lang": "formatjs compile-folder --ast build lang-compiled",
|
||||
"compile-lang": "formatjs compile-folder --ast build public/lang",
|
||||
"check-lang": "node scripts/check-lang.js",
|
||||
"download-country-names": "node scripts/download-country-names.js",
|
||||
"loadtest": "node scripts/loadtest.js",
|
||||
|
@ -56,36 +56,40 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "2.21.2",
|
||||
"@reduxjs/toolkit": "^1.5.1",
|
||||
"@fontsource/inter": "^4.5.0",
|
||||
"@fontsource/noto-sans-jp": "^4.5.0",
|
||||
"@fontsource/noto-sans-sc": "^4.5.0",
|
||||
"@fontsource/noto-sans-tc": "^4.5.0",
|
||||
"@prisma/client": "2.29.1",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chart.js": "^2.9.4",
|
||||
"classnames": "^2.3.1",
|
||||
"cookie": "^0.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^2.16.1",
|
||||
"date-fns-tz": "^1.0.12",
|
||||
"date-fns": "^2.23.0",
|
||||
"date-fns-tz": "^1.1.4",
|
||||
"detect-browser": "^5.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"formik": "^2.2.6",
|
||||
"immer": "^8.0.1",
|
||||
"ipaddr.js": "^2.0.0",
|
||||
"formik": "^2.2.9",
|
||||
"immer": "^9.0.5",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"is-localhost-ip": "^1.4.0",
|
||||
"isbot": "^3.0.26",
|
||||
"isbot": "^3.2.2",
|
||||
"jose": "2.0.5",
|
||||
"maxmind": "^4.3.1",
|
||||
"maxmind": "^4.3.2",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"next": "^10.1.3",
|
||||
"next": "10.2.2",
|
||||
"prompts": "2.4.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-intl": "^5.16.0",
|
||||
"react-intl": "^5.20.6",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-simple-maps": "^2.3.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-tooltip": "^4.2.18",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"react-use-measure": "^2.0.4",
|
||||
"react-window": "^1.8.6",
|
||||
"redux": "^4.1.0",
|
||||
|
@ -98,7 +102,7 @@
|
|||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^2.13.16",
|
||||
"@formatjs/cli": "^4.2.29",
|
||||
"@rollup/plugin-buble": "^0.21.3",
|
||||
"@rollup/plugin-node-resolve": "^11.2.1",
|
||||
"@rollup/plugin-replace": "^2.3.4",
|
||||
|
@ -106,29 +110,32 @@
|
|||
"cross-env": "^7.0.3",
|
||||
"del": "^6.0.0",
|
||||
"dotenv-cli": "^4.0.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-config-next": "^11.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^10.5.4",
|
||||
"lint-staged": "^11.0.0",
|
||||
"loadtest": "5.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.2.15",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-import": "^13.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.2.1",
|
||||
"postcss-rtlcss": "^3.3.2",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"prisma": "2.21.2",
|
||||
"rollup": "^2.45.2",
|
||||
"prisma": "2.29.1",
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-hashbang": "^2.2.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"stylelint": "^13.13.0",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-css-modules": "^2.2.0",
|
||||
"stylelint-config-prettier": "^8.0.1",
|
||||
"stylelint-config-recommended": "^5.0.0",
|
||||
"tar": "^6.0.5"
|
||||
"tar": "^6.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@ 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';
|
||||
import 'styles/index.css';
|
||||
import '@fontsource/inter/400.css';
|
||||
import '@fontsource/inter/600.css';
|
||||
|
||||
const Intl = ({ children }) => {
|
||||
const [locale] = useLocale();
|
||||
const { locale, messages } = useLocale();
|
||||
|
||||
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
||||
|
||||
|
@ -37,11 +38,6 @@ export default function App({ Component, pageProps }) {
|
|||
<link rel="icon" type="image/png" sizes="16x16" href={`${basePath}/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${basePath}/site.webmanifest`} />
|
||||
<link rel="mask-icon" href={`${basePath}/safari-pinned-tab.svg`} color="#5bbad5" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
|
|
@ -18,7 +18,7 @@ export default async (req, res) => {
|
|||
const data = {};
|
||||
|
||||
if (password) {
|
||||
data.password = await hashPassword(password);
|
||||
data.password = hashPassword(password);
|
||||
}
|
||||
|
||||
// Only admin can change these fields
|
||||
|
@ -51,7 +51,7 @@ export default async (req, res) => {
|
|||
return badRequest(res, 'Account already exists');
|
||||
}
|
||||
|
||||
const created = await createAccount({ username, password: await hashPassword(password) });
|
||||
const created = await createAccount({ username, password: hashPassword(password) });
|
||||
|
||||
return ok(res, created);
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ export default async (req, res) => {
|
|||
|
||||
if (req.method === 'POST') {
|
||||
const account = await getAccountById(user_id);
|
||||
const valid = await checkPassword(current_password, account.password);
|
||||
const valid = checkPassword(current_password, account.password);
|
||||
|
||||
if (!valid) {
|
||||
return badRequest(res, 'Current password is incorrect');
|
||||
}
|
||||
|
||||
const password = await hashPassword(new_password);
|
||||
const password = hashPassword(new_password);
|
||||
|
||||
const updated = await updateAccount(user_id, { password });
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export default async (req, res) => {
|
|||
const addr = ipaddr.parse(ip);
|
||||
const range = ipaddr.parseCIDR(i);
|
||||
|
||||
if (addr.match(range)) return true;
|
||||
if (addr.kind() === range[0].kind() && addr.match(range)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { resetWebsite } from 'lib/queries';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.query;
|
||||
const websiteId = +id;
|
||||
|
||||
if (req.method === 'POST') {
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
await resetWebsite(websiteId);
|
||||
|
||||
return ok(res);
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
};
|
|
@ -14,10 +14,18 @@ export default async (req, res) => {
|
|||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
||||
const distance = end_at - start_at;
|
||||
const prevStartDate = new Date(+start_at - distance);
|
||||
const prevEndDate = new Date(+end_at - distance);
|
||||
|
||||
const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url });
|
||||
const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url });
|
||||
|
||||
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||
obj[key] = Number(metrics[0][key]) || 0;
|
||||
obj[key] = {
|
||||
value: Number(metrics[0][key]) || 0,
|
||||
change: Number(metrics[0][key] - prevPeriod[0][key]) || 0,
|
||||
};
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
'postcss-rtlcss',
|
||||
'postcss-flexbugs-fixes',
|
||||
[
|
||||
'postcss-preset-env',
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
const hashPassword = password => {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const password = await hashPassword(process.env.ADMIN_PASSWORD || 'umami');
|
||||
const password = hashPassword(process.env.ADMIN_PASSWORD || 'umami');
|
||||
await prisma.account.upsert({
|
||||
where: { username: 'admin' },
|
||||
update: {},
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"AF":"Afganistan","AL":"Alb\u00e0nia","DE":"Alemanya","DZ":"Alg\u00e8ria","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Ant\u00e0rtida","AG":"Antigua i Barbuda","SA":"Ar\u00e0bia Saudita","AR":"Argentina","AM":"Arm\u00e8nia","AW":"Aruba","AU":"Austr\u00e0lia","AT":"\u00c0ustria","AZ":"Azerbaidjan","BS":"Bahames","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BY":"Belar\u00fas","BE":"B\u00e8lgica","BZ":"Belize","BJ":"Ben\u00edn","BM":"Bermudes","BT":"Bhutan","BO":"Bol\u00edvia","BA":"B\u00f2snia i Hercegovina","BW":"Botswana","BV":"Bouvet","BR":"Brasil","BN":"Brunei","BG":"Bulg\u00e0ria","BF":"Burkina Faso","BI":"Burundi","KH":"Cambodja","CM":"Camerun","CA":"Canad\u00e0","CV":"Cap Verd","BQ":"Carib Neerland\u00e8s","VA":"Ciutat del Vatic\u00e0","CO":"Col\u00f2mbia","KM":"Comores","CG":"Congo - Brazzaville","CD":"Congo - Kinshasa","KP":"Corea del Nord","KR":"Corea del Sud","CR":"Costa Rica","CI":"C\u00f4te d\u2019Ivoire","HR":"Cro\u00e0cia","CU":"Cuba","CW":"Cura\u00e7ao","DK":"Dinamarca","DJ":"Djibouti","DM":"Dominica","EG":"Egipte","SV":"El Salvador","AE":"Emirats \u00c0rabs Units","EC":"Equador","ER":"Eritrea","SK":"Eslov\u00e0quia","SI":"Eslov\u00e8nia","ES":"Espanya","US":"Estats Units","EE":"Est\u00f2nia","SZ":"eSwatini","ET":"Eti\u00f2pia","FJ":"Fiji","PH":"Filipines","FI":"Finl\u00e0ndia","FR":"Fran\u00e7a","GA":"Gabon","GM":"G\u00e0mbia","GE":"Ge\u00f2rgia","GH":"Ghana","GI":"Gibraltar","GR":"Gr\u00e8cia","GD":"Grenada","GL":"Groenl\u00e0ndia","GP":"Guadeloupe","GF":"Guaiana Francesa","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea Bissau","GQ":"Guinea Equatorial","GY":"Guyana","HT":"Hait\u00ed","HN":"Hondures","HK":"Hong Kong (RAE Xina)","HU":"Hongria","YE":"Iemen","CX":"Illa Christmas","RE":"Illa de la Reuni\u00f3","IM":"Illa de Man","HM":"Illa Heard i Illes McDonald","AX":"Illes \u00c5land","KY":"Illes Caiman","CC":"Illes Cocos","CK":"Illes Cook","FO":"Illes F\u00e8roe","GS":"Illes Ge\u00f2rgia del Sud i Sandwich del Sud","FK":"Illes Malvines","MP":"Illes Mariannes del Nord","MH":"Illes Marshall","UM":"Illes Perif\u00e8riques Menors dels EUA","PN":"Illes Pitcairn","SB":"Illes Salom\u00f3","TC":"Illes Turks i Caicos","VG":"Illes Verges Brit\u00e0niques","VI":"Illes Verges Nord-americanes","IN":"\u00cdndia","ID":"Indon\u00e8sia","IR":"Iran","IQ":"Iraq","IE":"Irlanda","IS":"Isl\u00e0ndia","IL":"Israel","IT":"It\u00e0lia","JM":"Jamaica","JP":"Jap\u00f3","JE":"Jersey","JO":"Jord\u00e0nia","KZ":"Kazakhstan","KE":"Kenya","KG":"Kirguizistan","KI":"Kiribati","KW":"Kuwait","LA":"Laos","LS":"Lesotho","LV":"Let\u00f2nia","LB":"L\u00edban","LR":"Lib\u00e8ria","LY":"L\u00edbia","LI":"Liechtenstein","LT":"Litu\u00e0nia","LU":"Luxemburg","MO":"Macau (RAE Xina)","MK":"Maced\u00f2nia del Nord","MG":"Madagascar","MY":"Mal\u00e0isia","MW":"Malawi","MV":"Maldives","ML":"Mali","MT":"Malta","MA":"Marroc","MQ":"Martinica","MU":"Maurici","MR":"Maurit\u00e0nia","YT":"Mayotte","MX":"M\u00e8xic","FM":"Micron\u00e8sia","MZ":"Mo\u00e7ambic","MD":"Mold\u00e0via","MC":"M\u00f2naco","MN":"Mong\u00f2lia","ME":"Montenegro","MS":"Montserrat","MM":"Myanmar (Birm\u00e0nia)","NA":"Nam\u00edbia","NR":"Nauru","NP":"Nepal","NI":"Nicaragua","NE":"N\u00edger","NG":"Nig\u00e8ria","NU":"Niue","NF":"Norfolk","NO":"Noruega","NC":"Nova Caled\u00f2nia","NZ":"Nova Zelanda","OM":"Oman","NL":"Pa\u00efsos Baixos","PK":"Pakistan","PW":"Palau","PA":"Panam\u00e0","PG":"Papua Nova Guinea","PY":"Paraguai","PE":"Per\u00fa","PF":"Polin\u00e8sia Francesa","PL":"Pol\u00f2nia","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","GB":"Regne Unit","CF":"Rep\u00fablica Centreafricana","ZA":"Rep\u00fablica de Sud-\u00e0frica","DO":"Rep\u00fablica Dominicana","RO":"Romania","RW":"Ruanda","RU":"R\u00fassia","EH":"S\u00e0hara Occidental","BL":"Saint Barth\u00e9lemy","KN":"Saint Christopher i Nevis","SH":"Saint Helena","LC":"Saint Lucia","MF":"Saint Martin","VC":"Saint Vincent i les Grenadines","PM":"Saint-Pierre-et-Miquelon","WS":"Samoa","AS":"Samoa Nord-americana","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 i Pr\u00edncipe","SN":"Senegal","RS":"S\u00e8rbia","SC":"Seychelles","SL":"Sierra Leone","SG":"Singapur","SX":"Sint Maarten","SY":"S\u00edria","SO":"Som\u00e0lia","LK":"Sri Lanka","SD":"Sudan","SS":"Sudan del Sud","SE":"Su\u00e8cia","CH":"Su\u00efssa","SR":"Surinam","SJ":"Svalbard i Jan Mayen","TJ":"Tadjikistan","TH":"Tail\u00e0ndia","TW":"Taiwan","TZ":"Tanz\u00e0nia","IO":"Territori Brit\u00e0nic de l\u2019Oce\u00e0 \u00cdndic","TF":"Territoris Australs Francesos","PS":"Territoris palestins","TL":"Timor Oriental","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinitat i Tobago","TN":"Tun\u00edsia","TM":"Turkmenistan","TR":"Turquia","TV":"Tuvalu","TD":"Txad","CZ":"Tx\u00e8quia","UA":"Ucra\u00efna","UG":"Uganda","UY":"Uruguai","UZ":"Uzbekistan","VU":"Vanuatu","VE":"Vene\u00e7uela","VN":"Vietnam","WF":"Wallis i Futuna","CL":"Xile","CN":"Xina","CY":"Xipre","ZM":"Z\u00e0mbia","ZW":"Zimb\u00e0bue"}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"AF":"Afganistan","AX":"\u00c5landski otoki","AL":"Albanija","DZ":"Al\u017eirija","AS":"Ameri\u0161ka Samoa","VI":"Ameri\u0161ki Devi\u0161ki otoki","AD":"Andora","AO":"Angola","AI":"Angvila","AQ":"Antarktika","AG":"Antigva in Barbuda","AR":"Argentina","AM":"Armenija","AW":"Aruba","AU":"Avstralija","AT":"Avstrija","AZ":"Azerbajd\u017ean","BS":"Bahami","BH":"Bahrajn","BD":"Banglade\u0161","BB":"Barbados","BE":"Belgija","BZ":"Belize","BY":"Belorusija","BJ":"Benin","BM":"Bermudi","BW":"Bocvana","BG":"Bolgarija","BO":"Bolivija","BA":"Bosna in Hercegovina","BV":"Bouvetov otok","CX":"Bo\u017ei\u010dni otok","BR":"Brazilija","VG":"Britanski Devi\u0161ki otoki","IO":"Britansko ozemlje v Indijskem oceanu","BN":"Brunej","BF":"Burkina Faso","BI":"Burundi","BT":"Butan","CF":"Centralnoafri\u0161ka republika","CY":"Ciper","CK":"Cookovi otoki","CW":"Cura\u00e7ao","TD":"\u010cad","CZ":"\u010ce\u0161ka","CL":"\u010cile","ME":"\u010crna gora","DK":"Danska","CD":"Demokrati\u010dna republika Kongo","DM":"Dominika","DO":"Dominikanska republika","DJ":"D\u017eibuti","EG":"Egipt","EC":"Ekvador","GQ":"Ekvatorialna Gvineja","ER":"Eritreja","EE":"Estonija","SZ":"Esvatini","ET":"Etiopija","FK":"Falklandski otoki","FO":"Ferski otoki","FJ":"Fid\u017ei","PH":"Filipini","FI":"Finska","FR":"Francija","GF":"Francoska Gvajana","PF":"Francoska Polinezija","TF":"Francosko ju\u017eno ozemlje","GA":"Gabon","GM":"Gambija","GH":"Gana","GI":"Gibraltar","GR":"Gr\u010dija","GD":"Grenada","GL":"Grenlandija","GE":"Gruzija","GP":"Guadeloupe","GU":"Guam","GG":"Guernsey","GY":"Gvajana","GT":"Gvatemala","GN":"Gvineja","GW":"Gvineja Bissau","HT":"Haiti","HM":"Heardov otok in McDonaldovi otoki","HN":"Honduras","HR":"Hrva\u0161ka","IN":"Indija","ID":"Indonezija","IQ":"Irak","IR":"Iran","IE":"Irska","IS":"Islandija","IT":"Italija","IL":"Izrael","JM":"Jamajka","JP":"Japonska","YE":"Jemen","JE":"Jersey","JO":"Jordanija","GS":"Ju\u017ena Georgia in Ju\u017eni Sandwichevi otoki","KR":"Ju\u017ena Koreja","SS":"Ju\u017eni Sudan","ZA":"Ju\u017enoafri\u0161ka republika","KY":"Kajmanski otoki","KH":"Kambod\u017ea","CM":"Kamerun","CA":"Kanada","QA":"Katar","KZ":"Kazahstan","KE":"Kenija","KG":"Kirgizistan","KI":"Kiribati","CN":"Kitajska","CC":"Kokosovi otoki","CO":"Kolumbija","KM":"Komori","CG":"Kongo - Brazzaville","CR":"Kostarika","CU":"Kuba","KW":"Kuvajt","LA":"Laos","LV":"Latvija","LS":"Lesoto","LB":"Libanon","LR":"Liberija","LY":"Libija","LI":"Lihten\u0161tajn","LT":"Litva","LU":"Luksemburg","MG":"Madagaskar","HU":"Mad\u017earska","MW":"Malavi","MV":"Maldivi","MY":"Malezija","ML":"Mali","MT":"Malta","MA":"Maroko","MH":"Marshallovi otoki","MQ":"Martinik","MU":"Mauritius","MR":"Mavretanija","YT":"Mayotte","MX":"Mehika","FM":"Mikronezija","MM":"Mjanmar (Burma)","MD":"Moldavija","MC":"Monako","MN":"Mongolija","MS":"Montserrat","MZ":"Mozambik","NA":"Namibija","NR":"Nauru","DE":"Nem\u010dija","NP":"Nepal","NE":"Niger","NG":"Nigerija","NI":"Nikaragva","NU":"Niue","NL":"Nizozemska","BQ":"Nizozemski Karibi","NF":"Norfol\u0161ki otok","NO":"Norve\u0161ka","NC":"Nova Kaledonija","NZ":"Nova Zelandija","OM":"Oman","IM":"Otok Man","TC":"Otoki Turks in Caicos","PK":"Pakistan","PW":"Palau","PS":"Palestinsko ozemlje","PA":"Panama","PG":"Papua Nova Gvineja","PY":"Paragvaj","PE":"Peru","PN":"Pitcairn","PL":"Poljska","PR":"Portoriko","PT":"Portugalska","HK":"Posebno administrativno obmo\u010dje LR Kitajske Hongkong","MO":"Posebno administrativno obmo\u010dje LR Kitajske Macao","RE":"Reunion","RO":"Romunija","RW":"Ruanda","RU":"Rusija","BL":"Saint Barth\u00e9lemy","KN":"Saint Kitts in Nevis","LC":"Saint Lucia","MF":"Saint Martin","PM":"Saint Pierre in Miquelon","VC":"Saint Vincent in Grenadine","SB":"Salomonovi otoki","SV":"Salvador","WS":"Samoa","SM":"San Marino","ST":"Sao Tome in Principe","SA":"Saudova Arabija","SC":"Sej\u0161eli","SN":"Senegal","KP":"Severna Koreja","MK":"Severna Makedonija","MP":"Severni Marianski otoki","SL":"Sierra Leone","SG":"Singapur","SX":"Sint Maarten","SY":"Sirija","CI":"Slonoko\u0161\u010dena obala","SK":"Slova\u0161ka","SI":"Slovenija","SO":"Somalija","RS":"Srbija","UM":"Stranski zunanji otoki Zdru\u017eenih dr\u017eav","SD":"Sudan","SR":"Surinam","SJ":"Svalbard in Jan Mayen","SH":"Sveta Helena","ES":"\u0160panija","LK":"\u0160rilanka","SE":"\u0160vedska","CH":"\u0160vica","TJ":"Tad\u017eikistan","TH":"Tajska","TW":"Tajvan","TZ":"Tanzanija","TL":"Timor-Leste","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad in Tobago","TN":"Tunizija","TR":"Tur\u010dija","TM":"Turkmenistan","TV":"Tuvalu","UG":"Uganda","UA":"Ukrajina","UY":"Urugvaj","UZ":"Uzbekistan","VU":"Vanuatu","VA":"Vatikan","VE":"Venezuela","VN":"Vietnam","WF":"Wallis in Futuna","EH":"Zahodna Sahara","ZM":"Zambija","US":"Zdru\u017eene dr\u017eave Amerike","AE":"Zdru\u017eeni arabski emirati","GB":"Zdru\u017eeno kraljestvo","CV":"Zelenortski otoki","ZW":"Zimbabve"}
|
|
@ -1,5 +1,5 @@
|
|||
require('dotenv').config();
|
||||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const chalk = require('chalk');
|
||||
const prompts = require('prompts');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
@ -25,11 +25,11 @@ const updateAccountByUsername = (username, data) => {
|
|||
};
|
||||
|
||||
const hashPassword = password => {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||
};
|
||||
|
||||
const changePassword = async (username, newPassword) => {
|
||||
const password = await hashPassword(newPassword);
|
||||
const password = hashPassword(newPassword);
|
||||
return updateAccountByUsername(username, { password });
|
||||
};
|
||||
|
||||
|
|
|
@ -12,9 +12,12 @@ body {
|
|||
flex: 1;
|
||||
|
||||
font-size: var(--font-size-normal);
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--gray900);
|
||||
background: var(--gray75);
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
.zh-CN {
|
||||
|
|
Loading…
Reference in New Issue