Merge pull request #234 from mikecao/dev

v0.62.0 Prisma update and version check
This commit is contained in:
Mike Cao 2020-09-29 21:45:12 -07:00 committed by GitHub
commit 241156d9ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 402 additions and 120 deletions

View File

@ -37,7 +37,7 @@ export default function DropDown({
return ( return (
<div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}> <div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}>
<div className={styles.value}> <div className={styles.value}>
{options.find(e => e.value === value)?.label || value} <div className={styles.text}>{options.find(e => e.value === value)?.label || value}</div>
<Icon icon={<Chevron />} className={styles.icon} size="small" /> <Icon icon={<Chevron />} className={styles.icon} size="small" />
</div> </div>
{showMenu && ( {showMenu && (

View File

@ -19,6 +19,10 @@
min-width: 160px; min-width: 160px;
} }
.text {
flex: 1;
}
.icon { .icon {
padding-left: 20px; padding-left: 20px;
} }

View File

@ -0,0 +1,47 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import useVersion from 'hooks/useVersion';
import styles from './UpdateNotice.module.css';
import ButtonLayout from '../layout/ButtonLayout';
import Button from './Button';
import useForceUpdate from '../../hooks/useForceUpdate';
export default function UpdateNotice() {
const forceUpdte = useForceUpdate();
const { hasUpdate, latest, updateCheck } = useVersion();
function handleViewClick() {
location.href = 'https://github.com/mikecao/umami/releases';
updateCheck();
forceUpdte();
}
function handleDismissClick() {
updateCheck();
forceUpdte();
}
if (!hasUpdate) {
return null;
}
return (
<div className={styles.notice}>
<div className={styles.message}>
<FormattedMessage
id="message.new-version-available"
defaultMessage="A new version of umami {version} is available!"
values={{ version: `v${latest}` }}
/>
</div>
<ButtonLayout>
<Button size="xsmall" variant="action" onClick={handleViewClick}>
<FormattedMessage id="button.view-details" defaultMessage="View details" />
</Button>
<Button size="xsmall" onClick={handleDismissClick}>
<FormattedMessage id="button.dismiss" defaultMessage="Dismiss" />
</Button>
</ButtonLayout>
</div>
);
}

View File

@ -0,0 +1,13 @@
.notice {
display: flex;
justify-content: center;
align-items: center;
padding-top: 10px;
font-size: var(--font-size-small);
font-weight: 600;
}
.message {
text-align: center;
margin-right: 20px;
}

View File

@ -6,6 +6,7 @@ import Link from 'components/common/Link';
import Icon from 'components/common/Icon'; import Icon from 'components/common/Icon';
import LanguageButton from 'components/settings/LanguageButton'; import LanguageButton from 'components/settings/LanguageButton';
import ThemeButton from 'components/settings/ThemeButton'; import ThemeButton from 'components/settings/ThemeButton';
import UpdateNotice from 'components/common/UpdateNotice';
import UserButton from 'components/settings/UserButton'; import UserButton from 'components/settings/UserButton';
import Logo from 'assets/logo.svg'; import Logo from 'assets/logo.svg';
import styles from './Header.module.css'; import styles from './Header.module.css';
@ -15,6 +16,7 @@ export default function Header() {
return ( return (
<header className="container"> <header className="container">
{user?.is_admin && <UpdateNotice />}
<div className={classNames(styles.header, 'row align-items-center')}> <div className={classNames(styles.header, 'row align-items-center')}>
<div className="col-12 col-md-12 col-lg-3"> <div className="col-12 col-md-12 col-lg-3">
<div className={styles.title}> <div className={styles.title}>

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import styles from './Page.module.css'; import styles from './Page.module.css';
export default function Page({ children }) { export default function Page({ className, children }) {
return <div className={styles.page}>{children}</div>; return <div className={classNames(styles.page, className)}>{children}</div>;
} }

View File

@ -4,4 +4,5 @@
align-items: center; align-items: center;
align-content: center; align-content: center;
min-height: 80px; min-height: 80px;
align-self: stretch;
} }

View File

@ -59,7 +59,7 @@ export default function WebsiteChart({
} }
return ( return (
<> <div className={styles.container}>
<WebsiteHeader websiteId={websiteId} token={token} title={title} showLink={showLink} /> <WebsiteHeader websiteId={websiteId} token={token} title={title} showLink={showLink} />
<div className={classNames(styles.header, 'row')}> <div className={classNames(styles.header, 'row')}>
<StickyHeader <StickyHeader
@ -92,7 +92,7 @@ export default function WebsiteChart({
/> />
</div> </div>
</div> </div>
</> </div>
); );
} }

View File

@ -1,6 +1,7 @@
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: stretch;
} }
.title { .title {

View File

@ -2,9 +2,9 @@ import React, { useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout'; import MenuLayout from 'components/layout/MenuLayout';
import WebsiteSettings from './WebsiteSettings'; import WebsiteSettings from '../settings/WebsiteSettings';
import AccountSettings from './AccountSettings'; import AccountSettings from '../settings/AccountSettings';
import ProfileSettings from './ProfileSettings'; import ProfileSettings from '../settings/ProfileSettings';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -26,7 +26,7 @@ export default function Settings() {
{ {
label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />, label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />,
value: ACCOUNTS, value: ACCOUNTS,
hidden: !user.is_admin, hidden: !user?.is_admin,
}, },
{ {
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />, label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,

View File

@ -0,0 +1,5 @@
.test {
border: 1px solid var(--gray200);
border-radius: 5px;
padding: 0 20px 20px 20px;
}

View File

@ -0,0 +1,94 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import Head from 'next/head';
import Link from 'next/link';
import Page from '../layout/Page';
import PageHeader from '../layout/PageHeader';
import useFetch from '../../hooks/useFetch';
import DropDown from '../common/DropDown';
import styles from './Test.module.css';
import WebsiteChart from '../metrics/WebsiteChart';
import EventsChart from '../metrics/EventsChart';
import Button from '../common/Button';
import EmptyPlaceholder from '../common/EmptyPlaceholder';
export default function TestConsole() {
const user = useSelector(state => state.user);
const [website, setWebsite] = useState();
const { data } = useFetch('/api/websites');
if (!data || !user?.is_admin) {
return null;
}
const options = data.map(({ name, website_id }) => ({ label: name, value: website_id }));
const selectedValue = options.find(({ value }) => value === website?.website_id)?.value;
function handleSelect(value) {
setWebsite(data.find(({ website_id }) => website_id === value));
}
function handleClick() {
window.umami('event (default)');
window.umami.trackView('/page-view', 'https://www.google.com');
window.umami.trackEvent('event (custom)', 'custom-type');
}
return (
<Page>
<Head>
{typeof window !== 'undefined' && website && (
<script async defer data-website-id={website.website_uuid} src="/umami.js" />
)}
</Head>
<PageHeader>
<div>Test Console</div>
<DropDown
value={selectedValue || 'Select website'}
options={options}
onChange={handleSelect}
/>
</PageHeader>
{!selectedValue && <EmptyPlaceholder msg="I hope you know what you're doing here" />}
{selectedValue && (
<>
<div className={classNames(styles.test, 'row')}>
<div className="col-4">
<PageHeader>Page links</PageHeader>
<div>
<Link href={`?page=1`}>
<a>page one</a>
</Link>
</div>
<div>
<Link href={`?page=2`}>
<a>page two</a>
</Link>
</div>
</div>
<div className="col-4">
<PageHeader>CSS events</PageHeader>
<Button id="primary-button" className="umami--click--primary-button" variant="action">
Send event
</Button>
</div>
<div className="col-4">
<PageHeader>Javascript events</PageHeader>
<Button id="manual-button" variant="action" onClick={handleClick}>
Run script
</Button>
</div>
</div>
<div className="row">
<div className="col-12">
<WebsiteChart websiteId={website.website_id} title={website.name} showLink />
<PageHeader>Events</PageHeader>
<EventsChart websiteId={website.website_id} />
</div>
</div>
</>
)}
</Page>
);
}

View File

@ -9,14 +9,14 @@ import Link from 'components/common/Link';
import Loading from 'components/common/Loading'; import Loading from 'components/common/Loading';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css'; import styles from './WebsiteDetails.module.css';
import PagesTable from './metrics/PagesTable'; import PagesTable from '../metrics/PagesTable';
import ReferrersTable from './metrics/ReferrersTable'; import ReferrersTable from '../metrics/ReferrersTable';
import BrowsersTable from './metrics/BrowsersTable'; import BrowsersTable from '../metrics/BrowsersTable';
import OSTable from './metrics/OSTable'; import OSTable from '../metrics/OSTable';
import DevicesTable from './metrics/DevicesTable'; import DevicesTable from '../metrics/DevicesTable';
import CountriesTable from './metrics/CountriesTable'; import CountriesTable from '../metrics/CountriesTable';
import EventsTable from './metrics/EventsTable'; import EventsTable from '../metrics/EventsTable';
import EventsChart from './metrics/EventsChart'; import EventsChart from '../metrics/EventsChart';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
import usePageQuery from 'hooks/usePageQuery'; import usePageQuery from 'hooks/usePageQuery';
@ -42,16 +42,17 @@ export default function WebsiteDetails({ websiteId, token }) {
} = usePageQuery(); } = usePageQuery();
const BackButton = () => ( const BackButton = () => (
<Link <div className={styles.backButton}>
key="back-button" <Link
className={styles.backButton} key="back-button"
href={router.pathname} href={router.pathname}
as={resolve({ view: undefined })} as={resolve({ view: undefined })}
icon={<Arrow />} icon={<Arrow />}
size="small" size="small"
> >
<FormattedMessage id="button.back" defaultMessage="Back" /> <FormattedMessage id="button.back" defaultMessage="Back" />
</Link> </Link>
</div>
); );
const menuOptions = [ const menuOptions = [

View File

@ -16,6 +16,9 @@
} }
.backButton { .backButton {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
} }

View File

@ -2,6 +2,7 @@
padding-bottom: 30px; padding-bottom: 30px;
border-bottom: 1px solid var(--gray300); border-bottom: 1px solid var(--gray300);
margin-bottom: 30px; margin-bottom: 30px;
align-self: stretch;
} }
.website:last-child { .website:last-child {

View File

@ -10,6 +10,7 @@ import TimezoneSetting from 'components/settings/TimezoneSetting';
import Dots from 'assets/ellipsis-h.svg'; import Dots from 'assets/ellipsis-h.svg';
import styles from './ProfileSettings.module.css'; import styles from './ProfileSettings.module.css';
import DateRangeSetting from './DateRangeSetting'; import DateRangeSetting from './DateRangeSetting';
import useEscapeKey from 'hooks/useEscapeKey';
export default function ProfileSettings() { export default function ProfileSettings() {
const user = useSelector(state => state.user); const user = useSelector(state => state.user);
@ -22,6 +23,10 @@ export default function ProfileSettings() {
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />); setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
} }
useEscapeKey(() => {
setChangePassword(false);
});
return ( return (
<> <>
<PageHeader> <PageHeader>

19
hooks/useEscapeKey.js Normal file
View File

@ -0,0 +1,19 @@
import { useEffect, useCallback } from 'react';
export default function useEscapeKey(handler) {
const escFunction = useCallback(event => {
if (event.keyCode === 27) {
handler(event);
}
}, []);
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, [escFunction]);
return null;
}

27
hooks/useVersion.js Normal file
View File

@ -0,0 +1,27 @@
import { useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import semver from 'semver';
import { getItem, setItem } from 'lib/web';
import { checkVersion } from 'redux/actions/app';
import { VERSION_CHECK } from 'lib/constants';
export default function useVersion() {
const dispatch = useDispatch();
const versions = useSelector(state => state.app.versions);
const lastCheck = getItem(VERSION_CHECK);
const { current, latest } = versions;
const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
const updateCheck = useCallback(() => {
setItem(VERSION_CHECK, { version: latest, time: Date.now() });
}, [versions]);
useEffect(() => {
if (!versions.latest) {
dispatch(checkVersion());
}
}, [versions]);
return { ...versions, hasUpdate, updateCheck };
}

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til udklipsholder", "button.copy-to-clipboard": "Kopier til udklipsholder",
"button.date-range": "Datointerval", "button.date-range": "Datointerval",
"button.delete": "Slet", "button.delete": "Slet",
"button.dismiss": "Dismiss",
"button.edit": "Rediger", "button.edit": "Rediger",
"button.login": "Log ind", "button.login": "Log ind",
"button.more": "Mere", "button.more": "Mere",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Få sporingskode", "message.get-tracking-code": "Få sporingskode",
"message.go-to-settings": "Gå til betjeningspanel", "message.go-to-settings": "Gå til betjeningspanel",
"message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.", "message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tilgængelig.", "message.no-data-available": "Ingen data tilgængelig.",
"message.no-websites-configured": "Du har ikke konfigureret nogen websteder.", "message.no-websites-configured": "Du har ikke konfigureret nogen websteder.",
"message.page-not-found": "Side ikke fundet.", "message.page-not-found": "Side ikke fundet.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "In die Zwischenablage kopieren", "button.copy-to-clipboard": "In die Zwischenablage kopieren",
"button.date-range": "Datumsbereich", "button.date-range": "Datumsbereich",
"button.delete": "Löschen", "button.delete": "Löschen",
"button.dismiss": "Dismiss",
"button.edit": "Bearbeiten", "button.edit": "Bearbeiten",
"button.login": "Anmelden", "button.login": "Anmelden",
"button.more": "Mehr", "button.more": "Mehr",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Erstelle Tracking Kennung", "message.get-tracking-code": "Erstelle Tracking Kennung",
"message.go-to-settings": "Zu den Einstellungen", "message.go-to-settings": "Zu den Einstellungen",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.", "message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Keine Daten vorhanden.", "message.no-data-available": "Keine Daten vorhanden.",
"message.no-websites-configured": "Es ist keine Webseite vorhanden.", "message.no-websites-configured": "Es ist keine Webseite vorhanden.",
"message.page-not-found": "Seite nicht gefunden.", "message.page-not-found": "Seite nicht gefunden.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο", "button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο",
"button.date-range": "Εύρος ημερομηνιών", "button.date-range": "Εύρος ημερομηνιών",
"button.delete": "Διαγραφή", "button.delete": "Διαγραφή",
"button.dismiss": "Dismiss",
"button.edit": "Επεξεργασία", "button.edit": "Επεξεργασία",
"button.login": "Είσοδος", "button.login": "Είσοδος",
"button.more": "Περισσότερα", "button.more": "Περισσότερα",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Λήψη κώδικα παρακολούθησης", "message.get-tracking-code": "Λήψη κώδικα παρακολούθησης",
"message.go-to-settings": "Μεταβείτε στις ρυθμίσεις", "message.go-to-settings": "Μεταβείτε στις ρυθμίσεις",
"message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.", "message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.", "message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.",
"message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.", "message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.",
"message.page-not-found": "Η σελίδα δεν βρέθηκε.", "message.page-not-found": "Η σελίδα δεν βρέθηκε.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copy to clipboard", "button.copy-to-clipboard": "Copy to clipboard",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Delete", "button.delete": "Delete",
"button.dismiss": "Dismiss",
"button.edit": "Edit", "button.edit": "Edit",
"button.login": "Login", "button.login": "Login",
"button.more": "More", "button.more": "More",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Get tracking code", "message.get-tracking-code": "Get tracking code",
"message.go-to-settings": "Go to settings", "message.go-to-settings": "Go to settings",
"message.incorrect-username-password": "Incorrect username/password.", "message.incorrect-username-password": "Incorrect username/password.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "No data available.", "message.no-data-available": "No data available.",
"message.no-websites-configured": "You don't have any websites configured.", "message.no-websites-configured": "You don't have any websites configured.",
"message.page-not-found": "Page not found.", "message.page-not-found": "Page not found.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copiar al portapapeles", "button.copy-to-clipboard": "Copiar al portapapeles",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Eliminar", "button.delete": "Eliminar",
"button.dismiss": "Dismiss",
"button.edit": "Editar", "button.edit": "Editar",
"button.login": "Iniciar sesión", "button.login": "Iniciar sesión",
"button.more": "Más", "button.more": "Más",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtener código de rastreo", "message.get-tracking-code": "Obtener código de rastreo",
"message.go-to-settings": "Ir a la configuración", "message.go-to-settings": "Ir a la configuración",
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.", "message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Sin información disponible.", "message.no-data-available": "Sin información disponible.",
"message.no-websites-configured": "No tienes ningún sitio configurado.", "message.no-websites-configured": "No tienes ningún sitio configurado.",
"message.page-not-found": "Page not found", "message.page-not-found": "Page not found",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til clipboard", "button.copy-to-clipboard": "Kopier til clipboard",
"button.date-range": "Vel dato", "button.date-range": "Vel dato",
"button.delete": "Sletta", "button.delete": "Sletta",
"button.dismiss": "Dismiss",
"button.edit": "Ger broyting", "button.edit": "Ger broyting",
"button.login": "Rita inn", "button.login": "Rita inn",
"button.more": "Meira", "button.more": "Meira",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Fá sporings kotu", "message.get-tracking-code": "Fá sporings kotu",
"message.go-to-settings": "Far til stillingar", "message.go-to-settings": "Far til stillingar",
"message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.", "message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Einki data tøk.", "message.no-data-available": "Einki data tøk.",
"message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.", "message.no-websites-configured": "Tú hevur ongar heimasíður stillaða til.",
"message.page-not-found": "Síðan bleiv ikki funnin.", "message.page-not-found": "Síðan bleiv ikki funnin.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copier dans le presse papier", "button.copy-to-clipboard": "Copier dans le presse papier",
"button.date-range": "Date range", "button.date-range": "Date range",
"button.delete": "Supprimer", "button.delete": "Supprimer",
"button.dismiss": "Dismiss",
"button.edit": "Modifier", "button.edit": "Modifier",
"button.login": "Connexion", "button.login": "Connexion",
"button.more": "Plus", "button.more": "Plus",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtenez le code de suivi", "message.get-tracking-code": "Obtenez le code de suivi",
"message.go-to-settings": "Aller aux paramètres", "message.go-to-settings": "Aller aux paramètres",
"message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.", "message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Pas de données disponibles.", "message.no-data-available": "Pas de données disponibles.",
"message.no-websites-configured": "Vous n'avez configuré aucun site Web.", "message.no-websites-configured": "Vous n'avez configuré aucun site Web.",
"message.page-not-found": "Page non trouvée.", "message.page-not-found": "Page non trouvée.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "クリップボードにコピー", "button.copy-to-clipboard": "クリップボードにコピー",
"button.date-range": "期間", "button.date-range": "期間",
"button.delete": "削除", "button.delete": "削除",
"button.dismiss": "Dismiss",
"button.edit": "編集", "button.edit": "編集",
"button.login": "ログイン", "button.login": "ログイン",
"button.more": "さらに表示", "button.more": "さらに表示",
@ -54,6 +55,7 @@
"message.get-tracking-code": "トラッキングコードを取得", "message.get-tracking-code": "トラッキングコードを取得",
"message.go-to-settings": "設定する", "message.go-to-settings": "設定する",
"message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。", "message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "データがありません。", "message.no-data-available": "データがありません。",
"message.no-websites-configured": "Webサイトが設定されていません。", "message.no-websites-configured": "Webサイトが設定されていません。",
"message.page-not-found": "ページが見つかりません。", "message.page-not-found": "ページが見つかりません。",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Хуулах", "button.copy-to-clipboard": "Хуулах",
"button.date-range": "Хугацааны мужид", "button.date-range": "Хугацааны мужид",
"button.delete": "Устгах", "button.delete": "Устгах",
"button.dismiss": "Dismiss",
"button.edit": "Засах", "button.edit": "Засах",
"button.login": "Нэвтрэх", "button.login": "Нэвтрэх",
"button.more": "Цааш", "button.more": "Цааш",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Мөрдөх код авах", "message.get-tracking-code": "Мөрдөх код авах",
"message.go-to-settings": "Тохиргоо руу очих", "message.go-to-settings": "Тохиргоо руу очих",
"message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.", "message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Өгөгдөл алга.", "message.no-data-available": "Өгөгдөл алга.",
"message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.", "message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.",
"message.page-not-found": "Хуудас олдсонгүй.", "message.page-not-found": "Хуудас олдсонгүй.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiëer naar klembord", "button.copy-to-clipboard": "Kopiëer naar klembord",
"button.date-range": "Datumbereik", "button.date-range": "Datumbereik",
"button.delete": "Verwijderen", "button.delete": "Verwijderen",
"button.dismiss": "Dismiss",
"button.edit": "Bewerken", "button.edit": "Bewerken",
"button.login": "Inloggen", "button.login": "Inloggen",
"button.more": "Toon meer", "button.more": "Toon meer",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Tracking code", "message.get-tracking-code": "Tracking code",
"message.go-to-settings": "Naar instellingen", "message.go-to-settings": "Naar instellingen",
"message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.", "message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Geen gegevens beschikbaar.", "message.no-data-available": "Geen gegevens beschikbaar.",
"message.no-websites-configured": "Je hebt geen websites ingesteld.", "message.no-websites-configured": "Je hebt geen websites ingesteld.",
"message.page-not-found": "Pagina niet gevonden.", "message.page-not-found": "Pagina niet gevonden.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Скопировать в буфер обмена", "button.copy-to-clipboard": "Скопировать в буфер обмена",
"button.date-range": "Диапазон дат", "button.date-range": "Диапазон дат",
"button.delete": "Удалить", "button.delete": "Удалить",
"button.dismiss": "Dismiss",
"button.edit": "Редактировать", "button.edit": "Редактировать",
"button.login": "Войти", "button.login": "Войти",
"button.more": "Больше", "button.more": "Больше",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Получить код отслеживания", "message.get-tracking-code": "Получить код отслеживания",
"message.go-to-settings": "Перейти к настройкам", "message.go-to-settings": "Перейти к настройкам",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.", "message.incorrect-username-password": "Неверное имя пользователя/пароль.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Нет данных.", "message.no-data-available": "Нет данных.",
"message.no-websites-configured": "У вас нет настроенных сайтов.", "message.no-websites-configured": "У вас нет настроенных сайтов.",
"message.page-not-found": "Страница не найдена.", "message.page-not-found": "Страница не найдена.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiera till urklipp", "button.copy-to-clipboard": "Kopiera till urklipp",
"button.date-range": "Datumomfång", "button.date-range": "Datumomfång",
"button.delete": "Radera", "button.delete": "Radera",
"button.dismiss": "Dismiss",
"button.edit": "Redigera", "button.edit": "Redigera",
"button.login": "Logga in", "button.login": "Logga in",
"button.more": "Mer", "button.more": "Mer",
@ -54,6 +55,7 @@
"message.get-tracking-code": "Visa spårningskod", "message.get-tracking-code": "Visa spårningskod",
"message.go-to-settings": "Gå till inställningar", "message.go-to-settings": "Gå till inställningar",
"message.incorrect-username-password": "Felaktikt användarnamn/lösenord.", "message.incorrect-username-password": "Felaktikt användarnamn/lösenord.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tillgänglig.", "message.no-data-available": "Ingen data tillgänglig.",
"message.no-websites-configured": "Du har inga webbsajter.", "message.no-websites-configured": "Du har inga webbsajter.",
"message.page-not-found": "Sidan kan inte hittas.", "message.page-not-found": "Sidan kan inte hittas.",

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Panoya kopyala", "button.copy-to-clipboard": "Panoya kopyala",
"button.date-range": "Tarih aralığı", "button.date-range": "Tarih aralığı",
"button.delete": "Sil", "button.delete": "Sil",
"button.dismiss": "Dismiss",
"button.edit": "Düzenle", "button.edit": "Düzenle",
"button.login": "Giriş Yap", "button.login": "Giriş Yap",
"button.more": "Detaylı göster", "button.more": "Detaylı göster",
@ -54,6 +55,7 @@
"message.get-tracking-code": "İzleme kodunu al", "message.get-tracking-code": "İzleme kodunu al",
"message.go-to-settings": "Ayarlara git", "message.go-to-settings": "Ayarlara git",
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.", "message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Henüz hiç veri yok.", "message.no-data-available": "Henüz hiç veri yok.",
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız", "message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
"message.page-not-found": "Sayfa bulunamadı.", "message.page-not-found": "Sayfa bulunamadı.",
@ -92,4 +94,4 @@
"title.edit-website": "Web sitesini düzenle", "title.edit-website": "Web sitesini düzenle",
"title.share-url": "Paylaşım adresi", "title.share-url": "Paylaşım adresi",
"title.tracking-code": "İzleme kodu" "title.tracking-code": "İzleme kodu"
} }

View File

@ -7,6 +7,7 @@
"button.copy-to-clipboard": "复制", "button.copy-to-clipboard": "复制",
"button.date-range": "多日", "button.date-range": "多日",
"button.delete": "删除", "button.delete": "删除",
"button.dismiss": "Dismiss",
"button.edit": "编辑", "button.edit": "编辑",
"button.login": "登录", "button.login": "登录",
"button.more": "更多", "button.more": "更多",
@ -54,6 +55,7 @@
"message.get-tracking-code": "获得跟踪代码", "message.get-tracking-code": "获得跟踪代码",
"message.go-to-settings": "去设置", "message.go-to-settings": "去设置",
"message.incorrect-username-password": "用户名密码不正确.", "message.incorrect-username-password": "用户名密码不正确.",
"message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "无可用数据.", "message.no-data-available": "无可用数据.",
"message.no-websites-configured": "你还没有设置任何网站.", "message.no-websites-configured": "你还没有设置任何网站.",
"message.page-not-found": "网页未找到.", "message.page-not-found": "网页未找到.",

View File

@ -3,6 +3,7 @@ export const LOCALE_CONFIG = 'umami.locale';
export const TIMEZONE_CONFIG = 'umami.timezone'; export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range'; export const DATE_RANGE_CONFIG = 'umami.date-range';
export const THEME_CONFIG = 'umami.theme'; export const THEME_CONFIG = 'umami.theme';
export const VERSION_CHECK = 'umami.version-check';
export const THEME_COLORS = { export const THEME_COLORS = {
light: { light: {

View File

@ -16,9 +16,13 @@ export function getDatabase() {
} }
export async function runQuery(query) { export async function runQuery(query) {
return query.catch(e => { return query
throw e; .catch(e => {
}); throw e;
})
.finally(async () => {
await prisma.$disconnect();
});
} }
export async function rawQuery(query, params) { export async function rawQuery(query, params) {
@ -285,8 +289,9 @@ export async function createAccount(data) {
); );
} }
export function getMetrics(website_id, start_at, end_at, url) { export function getMetrics(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {
@ -348,8 +353,10 @@ export function getPageviews(
); );
} }
export function getSessionMetrics(website_id, start_at, end_at, field, url) { export function getSessionMetrics(website_id, start_at, end_at, field, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {
@ -375,13 +382,15 @@ export function getSessionMetrics(website_id, start_at, end_at, field, url) {
); );
} }
export function getPageviewMetrics(website_id, start_at, end_at, field, table, domain, url) { export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { domain, url } = filters;
let domainFilter = ''; let domainFilter = '';
let urlFilter = ''; let urlFilter = '';
if (domain) { if (domain) {
domainFilter = `and referrer not like $${params.length + 1}`; domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
params.push(`%${domain}%`); params.push(`%${domain}%`);
} }
@ -420,8 +429,17 @@ export function getActiveVisitors(website_id) {
); );
} }
export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit = 'day', url) { export function getEvents(
website_id,
start_at,
end_at,
timezone = 'utc',
unit = 'day',
filters = {},
) {
const params = [website_id, start_at, end_at]; const params = [website_id, start_at, end_at];
const { url } = filters;
let urlFilter = ''; let urlFilter = '';
if (url) { if (url) {

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "0.58.0", "version": "0.62.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ", "description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",
@ -50,7 +50,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@prisma/client": "2.7.1", "@prisma/client": "2.8.0",
"@reduxjs/toolkit": "^1.4.0", "@reduxjs/toolkit": "^1.4.0",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -61,12 +61,12 @@
"date-fns": "^2.16.1", "date-fns": "^2.16.1",
"date-fns-tz": "^1.0.10", "date-fns-tz": "^1.0.10",
"detect-browser": "^5.1.1", "detect-browser": "^5.1.1",
"formik": "^2.1.5", "formik": "^2.1.6",
"immer": "^7.0.9", "immer": "^7.0.9",
"is-localhost-ip": "^1.4.0", "is-localhost-ip": "^1.4.0",
"isbot-fast": "^1.2.0", "isbot-fast": "^1.2.0",
"jose": "^2.0.2", "jose": "^2.0.2",
"maxmind": "^4.1.4", "maxmind": "^4.2.0",
"moment-timezone": "^0.5.31", "moment-timezone": "^0.5.31",
"next": "^9.5.3", "next": "^9.5.3",
"react": "^16.13.1", "react": "^16.13.1",
@ -80,6 +80,7 @@
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"request-ip": "^2.1.3", "request-ip": "^2.1.3",
"semver": "^7.3.2",
"thenby": "^1.3.4", "thenby": "^1.3.4",
"timezone-support": "^2.0.2", "timezone-support": "^2.0.2",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
@ -87,7 +88,7 @@
}, },
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^2.12.0", "@formatjs/cli": "^2.12.0",
"@prisma/cli": "2.7.1", "@prisma/cli": "2.8.0",
"@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.3", "@rollup/plugin-replace": "^2.3.3",
@ -113,7 +114,7 @@
"rollup": "^2.28.2", "rollup": "^2.28.2",
"rollup-plugin-hashbang": "^2.2.2", "rollup-plugin-hashbang": "^2.2.2",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"stylelint": "^13.7.1", "stylelint": "^13.7.2",
"stylelint-config-css-modules": "^2.2.0", "stylelint-config-css-modules": "^2.2.0",
"stylelint-config-prettier": "^8.0.1", "stylelint-config-prettier": "^8.0.1",
"stylelint-config-recommended": "^3.0.0", "stylelint-config-recommended": "^3.0.0",

View File

@ -21,7 +21,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at); const startDate = new Date(+start_at);
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
const events = await getEvents(websiteId, startDate, endDate, tz, unit, url); const events = await getEvents(websiteId, startDate, endDate, tz, unit, { url });
return ok(res, events); return ok(res, events);
} }

View File

@ -14,7 +14,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at); const startDate = new Date(+start_at);
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
const metrics = await getMetrics(websiteId, startDate, endDate, url); const metrics = await getMetrics(websiteId, startDate, endDate, { url });
const stats = Object.keys(metrics[0]).reduce((obj, key) => { const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = Number(metrics[0][key]) || 0; obj[key] = Number(metrics[0][key]) || 0;

View File

@ -42,7 +42,7 @@ export default async (req, res) => {
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
if (sessionColumns.includes(type)) { if (sessionColumns.includes(type)) {
const data = await getSessionMetrics(websiteId, startDate, endDate, type, url); const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url });
return ok(res, data); return ok(res, data);
} }
@ -54,8 +54,10 @@ export default async (req, res) => {
endDate, endDate,
getColumn(type), getColumn(type),
getTable(type), getTable(type),
domain, {
type !== 'url' ? url : undefined, domain,
url: type !== 'url' && url,
},
); );
return ok(res, data); return ok(res, data);

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteList from 'components/WebsiteList'; import WebsiteList from 'components/pages/WebsiteList';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function DashboardPage() { export default function DashboardPage() {

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import Settings from 'components/settings/Settings'; import Settings from 'components/pages/Settings';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function SettingsPage() { export default function SettingsPage() {

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/pages/WebsiteDetails';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
export default function SharePage() { export default function SharePage() {

View File

@ -1,56 +1,18 @@
import Head from 'next/head'; import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import TestConsole from 'components/pages/TestConsole';
import useRequireLogin from 'hooks/useRequireLogin';
export default function Test() { export default function TestPage() {
const router = useRouter(); const { loading } = useRequireLogin();
const { id } = router.query;
if (!id) { if (loading) {
return <h1>No id query specified.</h1>; return null;
}
function handleClick() {
window.umami('Custom event');
window.umami.pageView('/fake', 'https://www.google.com');
window.umami.pageEvent('pageEvent', 'custom-type');
} }
return ( return (
<> <Layout>
<Head> <TestConsole />
{typeof window !== 'undefined' && ( </Layout>
<script async defer data-website-id={id} src="/umami.js" />
)}
</Head>
<Layout>
<p>
Here you can test if your umami installation works. Open the network tab in your browser
developer console and watch for requests to the url <b>collect</b>. The links below should
trigger page views. Clicking on the button should trigger an event.
</p>
<h2>Page links</h2>
<Link href={`?id=${id}&q=1`}>
<a>Page One</a>
</Link>
<br />
<Link href={`?id=${id}&q=2`}>
<a>Page Two</a>
</Link>
<h2>Events</h2>
<button
id="primary-button"
className="otherClass umami--click--primary-button align-self-start"
type="button"
>
Button
</button>
<h2>Manual trigger</h2>
<button id="manual-button" type="button" onClick={handleClick}>
Button
</button>
</Layout>
</>
); );
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/pages/WebsiteDetails';
import useRequireLogin from 'hooks/useRequireLogin'; import useRequireLogin from 'hooks/useRequireLogin';
export default function DetailsPage() { export default function DetailsPage() {

View File

@ -7,6 +7,10 @@ const app = createSlice({
initialState: { initialState: {
locale: getItem(LOCALE_CONFIG) || 'en-US', locale: getItem(LOCALE_CONFIG) || 'en-US',
theme: getItem(THEME_CONFIG) || 'light', theme: getItem(THEME_CONFIG) || 'light',
versions: {
current: process.env.VERSION,
latest: null,
},
}, },
reducers: { reducers: {
setLocale(state, action) { setLocale(state, action) {
@ -17,9 +21,51 @@ const app = createSlice({
state.theme = action.payload; state.theme = action.payload;
return state; return state;
}, },
setVersions(state, action) {
state.versions = action.payload;
return state;
},
}, },
}); });
export const { setLocale, setTheme } = app.actions; export const { setLocale, setTheme, setVersions } = app.actions;
export default app.reducer; export default app.reducer;
export function checkVersion() {
return async (dispatch, getState) => {
const {
app: {
versions: { current },
},
} = getState();
const data = await fetch('https://api.github.com/repos/mikecao/umami/releases/latest', {
method: 'get',
headers: {
Accept: 'application/vnd.github.v3+json',
},
}).then(res => {
if (res.ok) {
return res.json();
}
return null;
});
if (!data) {
return;
}
const { tag_name } = data;
const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name;
return dispatch(
setVersions({
current,
latest,
}),
);
};
}

View File

@ -1193,15 +1193,15 @@
resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
"@prisma/cli@2.7.1": "@prisma/cli@2.8.0":
version "2.7.1" version "2.8.0"
resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.7.1.tgz#98f2cb434bb931341e6c6292c7bab601e5f842f8" resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.8.0.tgz#919d7f66023affa76d14823212b62a8512cfd37d"
integrity sha512-0uA+gWkNQ35DveVHDPltiTCTr4wcXtEhnPs463IEM+Xn8dTv9x0gtZiYHSuQM3t7uwlOxj1rurBsqSbiljynfQ== integrity sha512-Kg1C47d75jdEIMmJif8TMlv/2Ihx08E1qWp0euwoZhjd807HGnjgC9tJYjTfkdf+NMJSAUbvoPXKInEX0HoOMw==
"@prisma/client@2.7.1": "@prisma/client@2.8.0":
version "2.7.1" version "2.8.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.7.1.tgz#0a37ddff7fe80ae3a86dfa620c1141c8607be6c2" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.8.0.tgz#a0f7247786c9b6ee804437acf8215854c5eb3946"
integrity sha512-IEWDCuvIaQTira8/jAyf+uY+AuPPUFDIXMSN4zEA/gvoJv2woq7RmkaubS+NQVgDbbyOR6F3UcXLiFTYQDzZkQ== integrity sha512-5+GzRTkPnmv4OEV2tB8kwQt/xLLxBR/daJBcMt6pnnonJvrREsu0tSTdz2LJNPaj3kTT0fSS/OaeGMMdfVYSpw==
dependencies: dependencies:
pkg-up "^3.1.0" pkg-up "^3.1.0"
@ -4145,10 +4145,10 @@ for-own@^0.1.3:
dependencies: dependencies:
for-in "^1.0.1" for-in "^1.0.1"
formik@^2.1.5: formik@^2.1.6:
version "2.1.5" version "2.1.6"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8" resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.6.tgz#f723bfccb2c7abec886aa6a4930b360d20f1a0b3"
integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ== integrity sha512-m9DcxlZw/58p4xuhH3dzUzQWaC4dig0RKX7yNQOJt4VRhXn7p+YRrs3o17r3YwzvOLua3zC53VMbfupLsDwO5w==
dependencies: dependencies:
deepmerge "^2.1.1" deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
@ -5493,10 +5493,10 @@ mathml-tag-names@^2.1.3:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
maxmind@^4.1.4: maxmind@^4.2.0:
version "4.1.4" version "4.2.0"
resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.1.4.tgz#14fa0cf9a88f15b708edfd1378c5e49e0f0105c1" resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.2.0.tgz#912e5ec4a961807d20d7fb541160aeb5ea802c1c"
integrity sha512-DfcZPpc0XJVF1yypRpVqMs9JiSYYShVkfexSjwTbfqZMCEoQlCU83ooX9cRmWMUaLqm4zffi7HtZg7XqcJaL1A== integrity sha512-TADiE11Q10IjvLtlo05tTD52xLqfCJMhE3eYJHmpYIKg668STi/fQZGH9X3FpqpIP/2WPgKFxf899awFvfMtQA==
dependencies: dependencies:
tiny-lru "7.0.6" tiny-lru "7.0.6"
@ -8347,10 +8347,10 @@ stylelint-config-recommended@^3.0.0:
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657" resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657"
integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ== integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==
stylelint@^13.7.1: stylelint@^13.7.2:
version "13.7.1" version "13.7.2"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.1.tgz#bee97ee78d778a3f1dbe3f7397b76414973e263e" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486"
integrity sha512-qzqazcyRxrSRdmFuO0/SZOJ+LyCxYy0pwcvaOBBnl8/2VfHSMrtNIE+AnyJoyq6uKb+mt+hlgmVrvVi6G6XHfQ== integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==
dependencies: dependencies:
"@stylelint/postcss-css-in-js" "^0.37.2" "@stylelint/postcss-css-in-js" "^0.37.2"
"@stylelint/postcss-markdown" "^0.36.1" "@stylelint/postcss-markdown" "^0.36.1"