mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Merge branch 'dev' into patch-1
This commit is contained in:
commit
ee3c9debb2
130 changed files with 3107 additions and 2583 deletions
|
|
@ -18,6 +18,10 @@ export const filterOptions = [
|
|||
),
|
||||
value: '24hour',
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="label.yesterday" defaultMessage="Yesterday" />,
|
||||
value: '-1day',
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="label.this-week" defaultMessage="This week" />,
|
||||
value: '1week',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import Icon from './Icon';
|
||||
import External from 'assets/arrow-up-right-from-square.svg';
|
||||
import Icon from './Icon';
|
||||
import styles from './FilterLink.module.css';
|
||||
|
||||
export default function FilterLink({ id, value, label, externalUrl }) {
|
||||
|
|
@ -25,7 +25,7 @@ export default function FilterLink({ id, value, label, externalUrl }) {
|
|||
</a>
|
||||
</Link>
|
||||
{externalUrl && (
|
||||
<a href={externalUrl} target="_blank" rel="noreferrer noopener" className={styles.link}>
|
||||
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
|
||||
<Icon icon={<External />} className={styles.icon} />
|
||||
</a>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
import styles from './Loading.module.css';
|
||||
|
||||
function Loading({ className }) {
|
||||
function Loading({ className, overlay = false }) {
|
||||
return (
|
||||
<div className={classNames(styles.loading, className)}>
|
||||
<div className={classNames(styles.loading, { [styles.overlay]: overlay }, className)}>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
|
|
@ -15,6 +15,7 @@ function Loading({ className }) {
|
|||
|
||||
Loading.propTypes = {
|
||||
className: PropTypes.string,
|
||||
overlay: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.loading.overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
background: var(--gray400);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.loading div {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
|
@ -30,6 +38,10 @@
|
|||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.loading.overlay div {
|
||||
background: var(--gray900);
|
||||
}
|
||||
|
||||
.loading div + div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { setItem } from 'next-basics';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import useStore, { checkVersion } from 'store/version';
|
||||
import { setItem } from 'lib/web';
|
||||
import { REPO_URL, VERSION_CHECK } from 'lib/constants';
|
||||
import Button from './Button';
|
||||
import styles from './UpdateNotice.module.css';
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import FormLayout, {
|
|||
FormMessage,
|
||||
FormRow,
|
||||
} from 'components/layout/FormLayout';
|
||||
import Loading from 'components/common/Loading';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
const CONFIRMATION_WORD = 'DELETE';
|
||||
|
|
@ -29,8 +30,11 @@ const validate = ({ confirmation }) => {
|
|||
export default function DeleteForm({ values, onSave, onClose }) {
|
||||
const { del } = useApi();
|
||||
const [message, setMessage] = useState();
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const handleSubmit = async ({ type, id }) => {
|
||||
setDeleting(true);
|
||||
|
||||
const { ok, data } = await del(`/${type}/${id}`);
|
||||
|
||||
if (ok) {
|
||||
|
|
@ -39,11 +43,14 @@ export default function DeleteForm({ values, onSave, onClose }) {
|
|||
setMessage(
|
||||
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
|
||||
);
|
||||
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormLayout>
|
||||
{deleting && <Loading overlay />}
|
||||
<Formik
|
||||
initialValues={{ confirmation: '', ...values }}
|
||||
validate={validate}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { setItem } from 'next-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, {
|
||||
|
|
@ -11,7 +12,6 @@ import FormLayout, {
|
|||
} from 'components/layout/FormLayout';
|
||||
import Icon from 'components/common/Icon';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { setItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import { setUser } from 'store/app';
|
||||
import Logo from 'assets/logo.svg';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import HamburgerButton from 'components/common/HamburgerButton';
|
|||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import UserButton from 'components/settings/UserButton';
|
||||
import { HOMEPAGE_URL } from 'lib/constants';
|
||||
import useConfig from '/hooks/useConfig';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useUser from 'hooks/useUser';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Header.module.css';
|
||||
|
|
|
|||
|
|
@ -1,26 +1,6 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
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>;
|
||||
}
|
||||
export default function Page({ className, children }) {
|
||||
return <div className={classNames(styles.page, className)}>{children}</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Button from 'components/common/Button';
|
||||
import Times from 'assets/times.svg';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import styles from './FilterTags.module.css';
|
||||
|
||||
export default function FilterTags({ params, onClick }) {
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ const MetricCard = ({
|
|||
: !reverseColors
|
||||
? styles.negative
|
||||
: styles.positive
|
||||
}`}
|
||||
} ${change >= 0 ? styles.plusSign : ''}`}
|
||||
>
|
||||
{changeProps.x.interpolate(x => `${change >= 0 ? '+' : ''}${format(x)}`)}
|
||||
{changeProps.x.interpolate(x => format(x))}
|
||||
</animated.span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,3 +37,7 @@
|
|||
.change.negative {
|
||||
color: var(--red500);
|
||||
}
|
||||
|
||||
.change.plusSign::before {
|
||||
content: '+';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Tag from 'components/common/Tag';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import { paramFilter } from 'lib/filters';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import FilterButtons from '../common/FilterButtons';
|
||||
import MetricsTable from './MetricsTable';
|
||||
|
||||
const FILTER_COMBINED = 0;
|
||||
const FILTER_RAW = 1;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import classNames from 'classnames';
|
||||
import Button from 'components/common/Button';
|
||||
import { sortArrayByMap } from 'lib/array';
|
||||
import { firstBy } from 'thenby';
|
||||
import useDashboard, { saveDashboard } from 'store/dashboard';
|
||||
import styles from './DashboardEdit.module.css';
|
||||
|
||||
|
|
@ -21,8 +21,13 @@ export default function DashboardEdit({ websites }) {
|
|||
const { formatMessage } = useIntl();
|
||||
const [order, setOrder] = useState(websiteOrder || []);
|
||||
|
||||
const ordered = useMemo(() => sortArrayByMap(websites, order, 'website_id'), [websites, order]);
|
||||
|
||||
const ordered = useMemo(
|
||||
() =>
|
||||
websites
|
||||
.map(website => ({ ...website, order: order.indexOf(website.website_id) }))
|
||||
.sort(firstBy('order')),
|
||||
[websites, order],
|
||||
);
|
||||
|
||||
function handleWebsiteDrag({ destination, source }) {
|
||||
if (!destination || destination.index === source.index) return;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ export default function TestConsole() {
|
|||
const website = data.find(({ website_id }) => website_id === +websiteId);
|
||||
const selectedValue = options.find(({ value }) => value === website?.website_id)?.value;
|
||||
|
||||
console.log({ websiteId, data, options, website });
|
||||
|
||||
function handleSelect(value) {
|
||||
router.push(`/console/${value}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
|||
import Arrow from 'assets/arrow-right.svg';
|
||||
import styles from './WebsiteList.module.css';
|
||||
import useDashboard from 'store/dashboard';
|
||||
import { sortArrayByMap } from 'lib/array';
|
||||
import { useMemo } from 'react';
|
||||
import { firstBy } from 'thenby';
|
||||
|
||||
const messages = defineMessages({
|
||||
noWebsites: {
|
||||
|
|
@ -24,10 +24,11 @@ export default function WebsiteList({ websites, showCharts, limit }) {
|
|||
const { websiteOrder } = useDashboard();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
console.log({ websiteOrder });
|
||||
|
||||
const ordered = useMemo(
|
||||
() => sortArrayByMap(websites, websiteOrder, 'website_id'),
|
||||
() =>
|
||||
websites
|
||||
.map(website => ({ ...website, order: websiteOrder.indexOf(website.website_id) || 0 }))
|
||||
.sort(firstBy('order')),
|
||||
[websites, websiteOrder],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import { removeItem } from 'next-basics';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
import Icon from 'components/common/Icon';
|
||||
import User from 'assets/user.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
import { removeItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue