diff --git a/components/layout/Page.js b/components/layout/Page.js
index c9a928c52a..28492ddfe0 100644
--- a/components/layout/Page.js
+++ b/components/layout/Page.js
@@ -1,6 +1,7 @@
import React from 'react';
+import classNames from 'classnames';
import styles from './Page.module.css';
-export default function Page({ children }) {
- return
{children}
;
+export default function Page({ className, children }) {
+ return
{children}
;
}
diff --git a/components/layout/PageHeader.module.css b/components/layout/PageHeader.module.css
index 74f7d1a22e..263bd5b7d7 100644
--- a/components/layout/PageHeader.module.css
+++ b/components/layout/PageHeader.module.css
@@ -4,4 +4,5 @@
align-items: center;
align-content: center;
min-height: 80px;
+ align-self: stretch;
}
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index 99c03951bc..ea86ad3ec8 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -59,7 +59,7 @@ export default function WebsiteChart({
}
return (
- <>
+
- >
+
);
}
diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css
index 29f94670bb..0e947aea57 100644
--- a/components/metrics/WebsiteChart.module.css
+++ b/components/metrics/WebsiteChart.module.css
@@ -1,6 +1,7 @@
.container {
display: flex;
flex-direction: column;
+ align-self: stretch;
}
.title {
diff --git a/components/settings/Settings.js b/components/pages/Settings.js
similarity index 85%
rename from components/settings/Settings.js
rename to components/pages/Settings.js
index be9feb356b..35d039dfb0 100644
--- a/components/settings/Settings.js
+++ b/components/pages/Settings.js
@@ -2,9 +2,9 @@ import React, { useState } from 'react';
import { useRouter } from 'next/router';
import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout';
-import WebsiteSettings from './WebsiteSettings';
-import AccountSettings from './AccountSettings';
-import ProfileSettings from './ProfileSettings';
+import WebsiteSettings from '../settings/WebsiteSettings';
+import AccountSettings from '../settings/AccountSettings';
+import ProfileSettings from '../settings/ProfileSettings';
import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl';
@@ -26,7 +26,7 @@ export default function Settings() {
{
label:
,
value: ACCOUNTS,
- hidden: !user.is_admin,
+ hidden: !user?.is_admin,
},
{
label:
,
diff --git a/components/pages/Test.module.css b/components/pages/Test.module.css
new file mode 100644
index 0000000000..78977c555e
--- /dev/null
+++ b/components/pages/Test.module.css
@@ -0,0 +1,5 @@
+.test {
+ border: 1px solid var(--gray200);
+ border-radius: 5px;
+ padding: 0 20px 20px 20px;
+}
diff --git a/components/pages/TestConsole.js b/components/pages/TestConsole.js
new file mode 100644
index 0000000000..f6fa8a2331
--- /dev/null
+++ b/components/pages/TestConsole.js
@@ -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 (
+
+
+ {typeof window !== 'undefined' && website && (
+
+ )}
+
+
+ Test Console
+
+
+ {!selectedValue && }
+ {selectedValue && (
+ <>
+
+
+
+
CSS events
+
+ Send event
+
+
+
+
Javascript events
+
+ Run script
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/components/WebsiteDetails.js b/components/pages/WebsiteDetails.js
similarity index 87%
rename from components/WebsiteDetails.js
rename to components/pages/WebsiteDetails.js
index b38f55de95..e88c731f25 100644
--- a/components/WebsiteDetails.js
+++ b/components/pages/WebsiteDetails.js
@@ -9,14 +9,14 @@ import Link from 'components/common/Link';
import Loading from 'components/common/Loading';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css';
-import PagesTable from './metrics/PagesTable';
-import ReferrersTable from './metrics/ReferrersTable';
-import BrowsersTable from './metrics/BrowsersTable';
-import OSTable from './metrics/OSTable';
-import DevicesTable from './metrics/DevicesTable';
-import CountriesTable from './metrics/CountriesTable';
-import EventsTable from './metrics/EventsTable';
-import EventsChart from './metrics/EventsChart';
+import PagesTable from '../metrics/PagesTable';
+import ReferrersTable from '../metrics/ReferrersTable';
+import BrowsersTable from '../metrics/BrowsersTable';
+import OSTable from '../metrics/OSTable';
+import DevicesTable from '../metrics/DevicesTable';
+import CountriesTable from '../metrics/CountriesTable';
+import EventsTable from '../metrics/EventsTable';
+import EventsChart from '../metrics/EventsChart';
import useFetch from 'hooks/useFetch';
import usePageQuery from 'hooks/usePageQuery';
@@ -42,16 +42,17 @@ export default function WebsiteDetails({ websiteId, token }) {
} = usePageQuery();
const BackButton = () => (
-
}
- size="small"
- >
-
-
+
+ }
+ size="small"
+ >
+
+
+
);
const menuOptions = [
diff --git a/components/WebsiteDetails.module.css b/components/pages/WebsiteDetails.module.css
similarity index 92%
rename from components/WebsiteDetails.module.css
rename to components/pages/WebsiteDetails.module.css
index 0e1065c68a..750a1ac774 100644
--- a/components/WebsiteDetails.module.css
+++ b/components/pages/WebsiteDetails.module.css
@@ -16,6 +16,9 @@
}
.backButton {
+ display: flex;
+ justify-content: center;
+ align-items: center;
margin-bottom: 16px;
}
diff --git a/components/WebsiteList.js b/components/pages/WebsiteList.js
similarity index 100%
rename from components/WebsiteList.js
rename to components/pages/WebsiteList.js
diff --git a/components/WebsiteList.module.css b/components/pages/WebsiteList.module.css
similarity index 87%
rename from components/WebsiteList.module.css
rename to components/pages/WebsiteList.module.css
index 942b71b2e5..816c5f3631 100644
--- a/components/WebsiteList.module.css
+++ b/components/pages/WebsiteList.module.css
@@ -2,6 +2,7 @@
padding-bottom: 30px;
border-bottom: 1px solid var(--gray300);
margin-bottom: 30px;
+ align-self: stretch;
}
.website:last-child {
diff --git a/components/settings/ProfileSettings.js b/components/settings/ProfileSettings.js
index e23c73ed75..6fe18d193f 100644
--- a/components/settings/ProfileSettings.js
+++ b/components/settings/ProfileSettings.js
@@ -10,6 +10,7 @@ import TimezoneSetting from 'components/settings/TimezoneSetting';
import Dots from 'assets/ellipsis-h.svg';
import styles from './ProfileSettings.module.css';
import DateRangeSetting from './DateRangeSetting';
+import useEscapeKey from 'hooks/useEscapeKey';
export default function ProfileSettings() {
const user = useSelector(state => state.user);
@@ -22,6 +23,10 @@ export default function ProfileSettings() {
setMessage(
);
}
+ useEscapeKey(() => {
+ setChangePassword(false);
+ });
+
return (
<>
diff --git a/hooks/useEscapeKey.js b/hooks/useEscapeKey.js
new file mode 100644
index 0000000000..b8020c3198
--- /dev/null
+++ b/hooks/useEscapeKey.js
@@ -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;
+}
diff --git a/hooks/useVersion.js b/hooks/useVersion.js
new file mode 100644
index 0000000000..d8e3d699e9
--- /dev/null
+++ b/hooks/useVersion.js
@@ -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 };
+}
diff --git a/lang/da-DK.json b/lang/da-DK.json
index c4940630ee..cd96cb3faf 100644
--- a/lang/da-DK.json
+++ b/lang/da-DK.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til udklipsholder",
"button.date-range": "Datointerval",
"button.delete": "Slet",
+ "button.dismiss": "Dismiss",
"button.edit": "Rediger",
"button.login": "Log ind",
"button.more": "Mere",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Få sporingskode",
"message.go-to-settings": "Gå til betjeningspanel",
"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-websites-configured": "Du har ikke konfigureret nogen websteder.",
"message.page-not-found": "Side ikke fundet.",
diff --git a/lang/de-DE.json b/lang/de-DE.json
index f960d1eb19..7c45d1319a 100644
--- a/lang/de-DE.json
+++ b/lang/de-DE.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "In die Zwischenablage kopieren",
"button.date-range": "Datumsbereich",
"button.delete": "Löschen",
+ "button.dismiss": "Dismiss",
"button.edit": "Bearbeiten",
"button.login": "Anmelden",
"button.more": "Mehr",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Erstelle Tracking Kennung",
"message.go-to-settings": "Zu den Einstellungen",
"message.incorrect-username-password": "Falsches Passwort oder Benutzername.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Keine Daten vorhanden.",
"message.no-websites-configured": "Es ist keine Webseite vorhanden.",
"message.page-not-found": "Seite nicht gefunden.",
diff --git a/lang/el-GR.json b/lang/el-GR.json
index 4b1405e55e..14cd69ca12 100644
--- a/lang/el-GR.json
+++ b/lang/el-GR.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Αντιγραφή στο πρόχειρο",
"button.date-range": "Εύρος ημερομηνιών",
"button.delete": "Διαγραφή",
+ "button.dismiss": "Dismiss",
"button.edit": "Επεξεργασία",
"button.login": "Είσοδος",
"button.more": "Περισσότερα",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Λήψη κώδικα παρακολούθησης",
"message.go-to-settings": "Μεταβείτε στις ρυθμίσεις",
"message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.",
"message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.",
"message.page-not-found": "Η σελίδα δεν βρέθηκε.",
diff --git a/lang/en-US.json b/lang/en-US.json
index 54ee8cad69..7c513b958d 100644
--- a/lang/en-US.json
+++ b/lang/en-US.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copy to clipboard",
"button.date-range": "Date range",
"button.delete": "Delete",
+ "button.dismiss": "Dismiss",
"button.edit": "Edit",
"button.login": "Login",
"button.more": "More",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Get tracking code",
"message.go-to-settings": "Go to settings",
"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-websites-configured": "You don't have any websites configured.",
"message.page-not-found": "Page not found.",
diff --git a/lang/es-MX.json b/lang/es-MX.json
index ed8cf1a210..16d911eafc 100644
--- a/lang/es-MX.json
+++ b/lang/es-MX.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copiar al portapapeles",
"button.date-range": "Date range",
"button.delete": "Eliminar",
+ "button.dismiss": "Dismiss",
"button.edit": "Editar",
"button.login": "Iniciar sesión",
"button.more": "Más",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtener código de rastreo",
"message.go-to-settings": "Ir a la configuración",
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Sin información disponible.",
"message.no-websites-configured": "No tienes ningún sitio configurado.",
"message.page-not-found": "Page not found",
diff --git a/lang/fo-FO.json b/lang/fo-FO.json
index 800f91c7ae..db9e972bdd 100644
--- a/lang/fo-FO.json
+++ b/lang/fo-FO.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopier til clipboard",
"button.date-range": "Vel dato",
"button.delete": "Sletta",
+ "button.dismiss": "Dismiss",
"button.edit": "Ger broyting",
"button.login": "Rita inn",
"button.more": "Meira",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Fá sporings kotu",
"message.go-to-settings": "Far til stillingar",
"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-websites-configured": "Tú hevur ongar heimasíður stillaða til.",
"message.page-not-found": "Síðan bleiv ikki funnin.",
diff --git a/lang/fr-FR.json b/lang/fr-FR.json
index 8cfedb3320..762cae34d5 100644
--- a/lang/fr-FR.json
+++ b/lang/fr-FR.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Copier dans le presse papier",
"button.date-range": "Date range",
"button.delete": "Supprimer",
+ "button.dismiss": "Dismiss",
"button.edit": "Modifier",
"button.login": "Connexion",
"button.more": "Plus",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Obtenez le code de suivi",
"message.go-to-settings": "Aller aux paramètres",
"message.incorrect-username-password": "nom d'utilisateurs/mot de passe incorrect.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Pas de données disponibles.",
"message.no-websites-configured": "Vous n'avez configuré aucun site Web.",
"message.page-not-found": "Page non trouvée.",
diff --git a/lang/ja-JP.json b/lang/ja-JP.json
index b15e0c92bd..0c9cbd8976 100644
--- a/lang/ja-JP.json
+++ b/lang/ja-JP.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "クリップボードにコピー",
"button.date-range": "期間",
"button.delete": "削除",
+ "button.dismiss": "Dismiss",
"button.edit": "編集",
"button.login": "ログイン",
"button.more": "さらに表示",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "トラッキングコードを取得",
"message.go-to-settings": "設定する",
"message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "データがありません。",
"message.no-websites-configured": "Webサイトが設定されていません。",
"message.page-not-found": "ページが見つかりません。",
diff --git a/lang/mn-MN.json b/lang/mn-MN.json
index 377c81b108..b3c033cdd0 100644
--- a/lang/mn-MN.json
+++ b/lang/mn-MN.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Хуулах",
"button.date-range": "Хугацааны мужид",
"button.delete": "Устгах",
+ "button.dismiss": "Dismiss",
"button.edit": "Засах",
"button.login": "Нэвтрэх",
"button.more": "Цааш",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Мөрдөх код авах",
"message.go-to-settings": "Тохиргоо руу очих",
"message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Өгөгдөл алга.",
"message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.",
"message.page-not-found": "Хуудас олдсонгүй.",
diff --git a/lang/nl-NL.json b/lang/nl-NL.json
index 3ebfcd14b9..148fd32e48 100644
--- a/lang/nl-NL.json
+++ b/lang/nl-NL.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiëer naar klembord",
"button.date-range": "Datumbereik",
"button.delete": "Verwijderen",
+ "button.dismiss": "Dismiss",
"button.edit": "Bewerken",
"button.login": "Inloggen",
"button.more": "Toon meer",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Tracking code",
"message.go-to-settings": "Naar instellingen",
"message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Geen gegevens beschikbaar.",
"message.no-websites-configured": "Je hebt geen websites ingesteld.",
"message.page-not-found": "Pagina niet gevonden.",
diff --git a/lang/ru-RU.json b/lang/ru-RU.json
index b9e193369a..62a70a7c21 100644
--- a/lang/ru-RU.json
+++ b/lang/ru-RU.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Скопировать в буфер обмена",
"button.date-range": "Диапазон дат",
"button.delete": "Удалить",
+ "button.dismiss": "Dismiss",
"button.edit": "Редактировать",
"button.login": "Войти",
"button.more": "Больше",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Получить код отслеживания",
"message.go-to-settings": "Перейти к настройкам",
"message.incorrect-username-password": "Неверное имя пользователя/пароль.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Нет данных.",
"message.no-websites-configured": "У вас нет настроенных сайтов.",
"message.page-not-found": "Страница не найдена.",
diff --git a/lang/sv-SE.json b/lang/sv-SE.json
index 1a14b60d54..9e5e595809 100644
--- a/lang/sv-SE.json
+++ b/lang/sv-SE.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Kopiera till urklipp",
"button.date-range": "Datumomfång",
"button.delete": "Radera",
+ "button.dismiss": "Dismiss",
"button.edit": "Redigera",
"button.login": "Logga in",
"button.more": "Mer",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "Visa spårningskod",
"message.go-to-settings": "Gå till inställningar",
"message.incorrect-username-password": "Felaktikt användarnamn/lösenord.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Ingen data tillgänglig.",
"message.no-websites-configured": "Du har inga webbsajter.",
"message.page-not-found": "Sidan kan inte hittas.",
diff --git a/lang/tr-TR.json b/lang/tr-TR.json
index d8748359ff..2e84051d4a 100644
--- a/lang/tr-TR.json
+++ b/lang/tr-TR.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "Panoya kopyala",
"button.date-range": "Tarih aralığı",
"button.delete": "Sil",
+ "button.dismiss": "Dismiss",
"button.edit": "Düzenle",
"button.login": "Giriş Yap",
"button.more": "Detaylı göster",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "İzleme kodunu al",
"message.go-to-settings": "Ayarlara git",
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "Henüz hiç veri yok.",
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
"message.page-not-found": "Sayfa bulunamadı.",
@@ -92,4 +94,4 @@
"title.edit-website": "Web sitesini düzenle",
"title.share-url": "Paylaşım adresi",
"title.tracking-code": "İzleme kodu"
-}
\ No newline at end of file
+}
diff --git a/lang/zh-CN.json b/lang/zh-CN.json
index a51a42be8d..d5bf54cd87 100644
--- a/lang/zh-CN.json
+++ b/lang/zh-CN.json
@@ -7,6 +7,7 @@
"button.copy-to-clipboard": "复制",
"button.date-range": "多日",
"button.delete": "删除",
+ "button.dismiss": "Dismiss",
"button.edit": "编辑",
"button.login": "登录",
"button.more": "更多",
@@ -54,6 +55,7 @@
"message.get-tracking-code": "获得跟踪代码",
"message.go-to-settings": "去设置",
"message.incorrect-username-password": "用户名密码不正确.",
+ "message.new-version-available": "A new version of umami {version} is available!",
"message.no-data-available": "无可用数据.",
"message.no-websites-configured": "你还没有设置任何网站.",
"message.page-not-found": "网页未找到.",
diff --git a/lib/constants.js b/lib/constants.js
index 8acbf0b661..b16abdc5c7 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -3,6 +3,7 @@ export const LOCALE_CONFIG = 'umami.locale';
export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range';
export const THEME_CONFIG = 'umami.theme';
+export const VERSION_CHECK = 'umami.version-check';
export const THEME_COLORS = {
light: {
diff --git a/lib/queries.js b/lib/queries.js
index af2b7f5bd4..d10777fd4d 100644
--- a/lib/queries.js
+++ b/lib/queries.js
@@ -16,9 +16,13 @@ export function getDatabase() {
}
export async function runQuery(query) {
- return query.catch(e => {
- throw e;
- });
+ return query
+ .catch(e => {
+ throw e;
+ })
+ .finally(async () => {
+ await prisma.$disconnect();
+ });
}
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 { url } = filters;
let urlFilter = '';
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 { url } = filters;
+
let urlFilter = '';
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 { domain, url } = filters;
+
let domainFilter = '';
let urlFilter = '';
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}%`);
}
@@ -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 { url } = filters;
+
let urlFilter = '';
if (url) {
diff --git a/package.json b/package.json
index 114d23ed66..68a31c0b40 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.58.0",
+ "version": "0.62.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao ",
"license": "MIT",
@@ -50,7 +50,7 @@
}
},
"dependencies": {
- "@prisma/client": "2.7.1",
+ "@prisma/client": "2.8.0",
"@reduxjs/toolkit": "^1.4.0",
"bcrypt": "^5.0.0",
"chalk": "^4.1.0",
@@ -61,12 +61,12 @@
"date-fns": "^2.16.1",
"date-fns-tz": "^1.0.10",
"detect-browser": "^5.1.1",
- "formik": "^2.1.5",
+ "formik": "^2.1.6",
"immer": "^7.0.9",
"is-localhost-ip": "^1.4.0",
"isbot-fast": "^1.2.0",
"jose": "^2.0.2",
- "maxmind": "^4.1.4",
+ "maxmind": "^4.2.0",
"moment-timezone": "^0.5.31",
"next": "^9.5.3",
"react": "^16.13.1",
@@ -80,6 +80,7 @@
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"request-ip": "^2.1.3",
+ "semver": "^7.3.2",
"thenby": "^1.3.4",
"timezone-support": "^2.0.2",
"tinycolor2": "^1.4.2",
@@ -87,7 +88,7 @@
},
"devDependencies": {
"@formatjs/cli": "^2.12.0",
- "@prisma/cli": "2.7.1",
+ "@prisma/cli": "2.8.0",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.3",
@@ -113,7 +114,7 @@
"rollup": "^2.28.2",
"rollup-plugin-hashbang": "^2.2.2",
"rollup-plugin-terser": "^7.0.2",
- "stylelint": "^13.7.1",
+ "stylelint": "^13.7.2",
"stylelint-config-css-modules": "^2.2.0",
"stylelint-config-prettier": "^8.0.1",
"stylelint-config-recommended": "^3.0.0",
diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js
index da610f178b..0498052ff2 100644
--- a/pages/api/website/[id]/events.js
+++ b/pages/api/website/[id]/events.js
@@ -21,7 +21,7 @@ export default async (req, res) => {
const startDate = new Date(+start_at);
const endDate = new Date(+end_at);
- const events = await getEvents(websiteId, startDate, endDate, tz, unit, url);
+ const events = await getEvents(websiteId, startDate, endDate, tz, unit, { url });
return ok(res, events);
}
diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js
index f7178bf4e0..91a0f4cf56 100644
--- a/pages/api/website/[id]/metrics.js
+++ b/pages/api/website/[id]/metrics.js
@@ -14,7 +14,7 @@ export default async (req, res) => {
const startDate = new Date(+start_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) => {
obj[key] = Number(metrics[0][key]) || 0;
diff --git a/pages/api/website/[id]/rankings.js b/pages/api/website/[id]/rankings.js
index 3def9d3af6..17aa6daacb 100644
--- a/pages/api/website/[id]/rankings.js
+++ b/pages/api/website/[id]/rankings.js
@@ -42,7 +42,7 @@ export default async (req, res) => {
const endDate = new Date(+end_at);
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);
}
@@ -54,8 +54,10 @@ export default async (req, res) => {
endDate,
getColumn(type),
getTable(type),
- domain,
- type !== 'url' ? url : undefined,
+ {
+ domain,
+ url: type !== 'url' && url,
+ },
);
return ok(res, data);
diff --git a/pages/dashboard/[[...id]].js b/pages/dashboard/[[...id]].js
index 90f0849253..d3ce1301ce 100644
--- a/pages/dashboard/[[...id]].js
+++ b/pages/dashboard/[[...id]].js
@@ -1,7 +1,7 @@
import React from 'react';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
-import WebsiteList from 'components/WebsiteList';
+import WebsiteList from 'components/pages/WebsiteList';
import useRequireLogin from 'hooks/useRequireLogin';
export default function DashboardPage() {
diff --git a/pages/settings/index.js b/pages/settings/index.js
index 65eff46e43..b4bb224835 100644
--- a/pages/settings/index.js
+++ b/pages/settings/index.js
@@ -1,6 +1,6 @@
import React from 'react';
import Layout from 'components/layout/Layout';
-import Settings from 'components/settings/Settings';
+import Settings from 'components/pages/Settings';
import useRequireLogin from 'hooks/useRequireLogin';
export default function SettingsPage() {
diff --git a/pages/share/[...id].js b/pages/share/[...id].js
index c4fa9a634b..85455af4ed 100644
--- a/pages/share/[...id].js
+++ b/pages/share/[...id].js
@@ -1,7 +1,7 @@
import React from 'react';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
-import WebsiteDetails from 'components/WebsiteDetails';
+import WebsiteDetails from 'components/pages/WebsiteDetails';
import useFetch from 'hooks/useFetch';
export default function SharePage() {
diff --git a/pages/test.js b/pages/test.js
index 820a31879c..35b88ce3fd 100644
--- a/pages/test.js
+++ b/pages/test.js
@@ -1,56 +1,18 @@
-import Head from 'next/head';
-import Link from 'next/link';
-import { useRouter } from 'next/router';
+import React from 'react';
import Layout from 'components/layout/Layout';
+import TestConsole from 'components/pages/TestConsole';
+import useRequireLogin from 'hooks/useRequireLogin';
-export default function Test() {
- const router = useRouter();
- const { id } = router.query;
+export default function TestPage() {
+ const { loading } = useRequireLogin();
- if (!id) {
- return No id query specified. ;
- }
-
- function handleClick() {
- window.umami('Custom event');
- window.umami.pageView('/fake', 'https://www.google.com');
- window.umami.pageEvent('pageEvent', 'custom-type');
+ if (loading) {
+ return null;
}
return (
- <>
-
- {typeof window !== 'undefined' && (
-
- )}
-
-
-
- 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 collect . The links below should
- trigger page views. Clicking on the button should trigger an event.
-
- Page links
-
- Page One
-
-
-
- Page Two
-
- Events
-
- Button
-
- Manual trigger
-
- Button
-
-
- >
+
+
+
);
}
diff --git a/pages/website/[...id].js b/pages/website/[...id].js
index 0add10aae8..90f4f492c2 100644
--- a/pages/website/[...id].js
+++ b/pages/website/[...id].js
@@ -1,7 +1,7 @@
import React from 'react';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
-import WebsiteDetails from 'components/WebsiteDetails';
+import WebsiteDetails from 'components/pages/WebsiteDetails';
import useRequireLogin from 'hooks/useRequireLogin';
export default function DetailsPage() {
diff --git a/redux/actions/app.js b/redux/actions/app.js
index 6bcba2f00f..490bd7f102 100644
--- a/redux/actions/app.js
+++ b/redux/actions/app.js
@@ -7,6 +7,10 @@ const app = createSlice({
initialState: {
locale: getItem(LOCALE_CONFIG) || 'en-US',
theme: getItem(THEME_CONFIG) || 'light',
+ versions: {
+ current: process.env.VERSION,
+ latest: null,
+ },
},
reducers: {
setLocale(state, action) {
@@ -17,9 +21,51 @@ const app = createSlice({
state.theme = action.payload;
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 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,
+ }),
+ );
+ };
+}
diff --git a/yarn.lock b/yarn.lock
index caed31d862..00d976ebd1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1193,15 +1193,15 @@
resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
-"@prisma/cli@2.7.1":
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.7.1.tgz#98f2cb434bb931341e6c6292c7bab601e5f842f8"
- integrity sha512-0uA+gWkNQ35DveVHDPltiTCTr4wcXtEhnPs463IEM+Xn8dTv9x0gtZiYHSuQM3t7uwlOxj1rurBsqSbiljynfQ==
+"@prisma/cli@2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.8.0.tgz#919d7f66023affa76d14823212b62a8512cfd37d"
+ integrity sha512-Kg1C47d75jdEIMmJif8TMlv/2Ihx08E1qWp0euwoZhjd807HGnjgC9tJYjTfkdf+NMJSAUbvoPXKInEX0HoOMw==
-"@prisma/client@2.7.1":
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.7.1.tgz#0a37ddff7fe80ae3a86dfa620c1141c8607be6c2"
- integrity sha512-IEWDCuvIaQTira8/jAyf+uY+AuPPUFDIXMSN4zEA/gvoJv2woq7RmkaubS+NQVgDbbyOR6F3UcXLiFTYQDzZkQ==
+"@prisma/client@2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.8.0.tgz#a0f7247786c9b6ee804437acf8215854c5eb3946"
+ integrity sha512-5+GzRTkPnmv4OEV2tB8kwQt/xLLxBR/daJBcMt6pnnonJvrREsu0tSTdz2LJNPaj3kTT0fSS/OaeGMMdfVYSpw==
dependencies:
pkg-up "^3.1.0"
@@ -4145,10 +4145,10 @@ for-own@^0.1.3:
dependencies:
for-in "^1.0.1"
-formik@^2.1.5:
- version "2.1.5"
- resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8"
- integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ==
+formik@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.6.tgz#f723bfccb2c7abec886aa6a4930b360d20f1a0b3"
+ integrity sha512-m9DcxlZw/58p4xuhH3dzUzQWaC4dig0RKX7yNQOJt4VRhXn7p+YRrs3o17r3YwzvOLua3zC53VMbfupLsDwO5w==
dependencies:
deepmerge "^2.1.1"
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"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
-maxmind@^4.1.4:
- version "4.1.4"
- resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.1.4.tgz#14fa0cf9a88f15b708edfd1378c5e49e0f0105c1"
- integrity sha512-DfcZPpc0XJVF1yypRpVqMs9JiSYYShVkfexSjwTbfqZMCEoQlCU83ooX9cRmWMUaLqm4zffi7HtZg7XqcJaL1A==
+maxmind@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.2.0.tgz#912e5ec4a961807d20d7fb541160aeb5ea802c1c"
+ integrity sha512-TADiE11Q10IjvLtlo05tTD52xLqfCJMhE3eYJHmpYIKg668STi/fQZGH9X3FpqpIP/2WPgKFxf899awFvfMtQA==
dependencies:
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"
integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==
-stylelint@^13.7.1:
- version "13.7.1"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.1.tgz#bee97ee78d778a3f1dbe3f7397b76414973e263e"
- integrity sha512-qzqazcyRxrSRdmFuO0/SZOJ+LyCxYy0pwcvaOBBnl8/2VfHSMrtNIE+AnyJoyq6uKb+mt+hlgmVrvVi6G6XHfQ==
+stylelint@^13.7.2:
+ version "13.7.2"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486"
+ integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==
dependencies:
"@stylelint/postcss-css-in-js" "^0.37.2"
"@stylelint/postcss-markdown" "^0.36.1"