mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 18:00:17 +01:00
Update realtime chart.
This commit is contained in:
parent
e64a555652
commit
fdc92d087b
@ -1,8 +1,8 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function ActiveUsers({ websiteId, token, className }) {
|
||||
const { data } = useFetch(`/api/website/${websiteId}/active`, { token }, { interval: 60000 });
|
||||
|
@ -39,6 +39,8 @@ export default function BarChart({
|
||||
const w = canvas.current.width;
|
||||
|
||||
switch (unit) {
|
||||
case 'minute':
|
||||
return dateFormat(d, 'h:mm', locale);
|
||||
case 'hour':
|
||||
return dateFormat(d, 'ha', locale);
|
||||
case 'day':
|
||||
|
@ -26,7 +26,7 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
|
||||
data: { datasets },
|
||||
} = chart;
|
||||
|
||||
datasets[0].data = data.uniques;
|
||||
datasets[0].data = data.sessions;
|
||||
datasets[0].label = intl.formatMessage({
|
||||
id: 'metrics.unique-visitors',
|
||||
defaultMessage: 'Unique visitors',
|
||||
@ -56,7 +56,7 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
|
||||
id: 'metrics.unique-visitors',
|
||||
defaultMessage: 'Unique visitors',
|
||||
}),
|
||||
data: data.uniques,
|
||||
data: data.sessions,
|
||||
lineTension: 0,
|
||||
backgroundColor: colors.visitors.background,
|
||||
borderColor: colors.visitors.border,
|
||||
|
@ -45,14 +45,14 @@ export default function WebsiteChart({
|
||||
{ onDataLoad, update: [modified] },
|
||||
);
|
||||
|
||||
const [pageviews, uniques] = useMemo(() => {
|
||||
const chartData = useMemo(() => {
|
||||
if (data) {
|
||||
return [
|
||||
getDateArray(data.pageviews, startDate, endDate, unit),
|
||||
getDateArray(data.uniques, startDate, endDate, unit),
|
||||
];
|
||||
return {
|
||||
pageviews: getDateArray(data.pageviews, startDate, endDate, unit),
|
||||
sessions: getDateArray(data.sessions, startDate, endDate, unit),
|
||||
};
|
||||
}
|
||||
return [[], []];
|
||||
return { pageviews: [], sessions: [] };
|
||||
}, [data]);
|
||||
|
||||
function handleCloseFilter() {
|
||||
@ -87,7 +87,7 @@ export default function WebsiteChart({
|
||||
{error && <ErrorMessage />}
|
||||
<PageviewsChart
|
||||
websiteId={websiteId}
|
||||
data={{ pageviews, uniques }}
|
||||
data={chartData}
|
||||
unit={unit}
|
||||
records={getDateLength(startDate, endDate, unit)}
|
||||
loading={loading}
|
||||
|
@ -1,36 +1,101 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Page from '../layout/Page';
|
||||
import PageHeader from '../layout/PageHeader';
|
||||
import useFetch from '../../hooks/useFetch';
|
||||
import DropDown from '../common/DropDown';
|
||||
import RealtimeChart from '../metrics/RealtimeChart';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { subMinutes, startOfMinute, parseISO, format } from 'date-fns';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import PageviewsChart from '../metrics/PageviewsChart';
|
||||
import { getDateArray } from '../../lib/date';
|
||||
|
||||
export default function TestConsole() {
|
||||
const user = useSelector(state => state.user);
|
||||
function filterTime(data, time) {
|
||||
return data.filter(({ created_at }) => new Date(created_at).getTime() > time);
|
||||
}
|
||||
|
||||
function mapData(data) {
|
||||
let last = 0;
|
||||
const arr = [];
|
||||
|
||||
data.reduce((obj, val) => {
|
||||
const { created_at } = val;
|
||||
const t = startOfMinute(parseISO(created_at));
|
||||
if (t.getTime() > last) {
|
||||
obj = { t: format(t, 'yyyy-LL-dd HH:mm:00'), y: 1 };
|
||||
arr.push(obj);
|
||||
last = t;
|
||||
} else {
|
||||
obj.y += 1;
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
export default function RealtimeDashboard() {
|
||||
const [data, setData] = useState();
|
||||
const [website, setWebsite] = useState();
|
||||
const { data } = useFetch('/api/websites');
|
||||
const { data: init, loading } = useFetch('/api/realtime', { type: 'init' });
|
||||
const { data: updates } = useFetch(
|
||||
'/api/realtime',
|
||||
{ type: 'update' },
|
||||
{ disabled: !init?.token, interval: 5000, headers: { 'x-umami-token': init?.token } },
|
||||
);
|
||||
|
||||
if (!data || !user?.is_admin) {
|
||||
const chartData = useMemo(() => {
|
||||
if (data) {
|
||||
const endDate = startOfMinute(new Date());
|
||||
const startDate = subMinutes(endDate, 30);
|
||||
const unit = 'minute';
|
||||
|
||||
console.log({ data });
|
||||
|
||||
return {
|
||||
pageviews: getDateArray(mapData(data.pageviews), startDate, endDate, unit),
|
||||
sessions: getDateArray(mapData(data.sessions), startDate, endDate, unit),
|
||||
};
|
||||
}
|
||||
return { pageviews: [], sessions: [] };
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (init && !data) {
|
||||
setData(init.data);
|
||||
} else if (updates) {
|
||||
const { pageviews, sessions, events } = updates;
|
||||
const time = subMinutes(startOfMinute(new Date()), 30).getTime();
|
||||
setData(state => ({
|
||||
pageviews: filterTime(state.pageviews, time).concat(pageviews),
|
||||
sessions: filterTime(state.sessions, time).concat(sessions),
|
||||
events: filterTime(state.events, time).concat(events),
|
||||
}));
|
||||
}
|
||||
}, [updates, init]);
|
||||
|
||||
if (!init || loading || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const options = [{ label: 'All websites', value: 0 }].concat(
|
||||
data.map(({ name, website_id }) => ({ label: name, value: website_id })),
|
||||
);
|
||||
const { websites } = init;
|
||||
|
||||
const options = [
|
||||
{ label: <FormattedMessage id="label.all-websites" defaultMessage="All websites" />, value: 0 },
|
||||
].concat(websites.map(({ name, website_id }) => ({ label: name, value: website_id })));
|
||||
const selectedValue = options.find(({ value }) => value === website?.website_id)?.value || 0;
|
||||
|
||||
function handleSelect(value) {
|
||||
setWebsite(data.find(({ website_id }) => website_id === value));
|
||||
setWebsite(websites.find(({ website_id }) => website_id === value));
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader>
|
||||
<div>Real time</div>
|
||||
<div>
|
||||
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
|
||||
</div>
|
||||
<DropDown value={selectedValue} options={options} onChange={handleSelect} />
|
||||
</PageHeader>
|
||||
<RealtimeChart websiteId={website?.website_id} />
|
||||
<PageviewsChart websiteId={website?.website_id} data={chartData} unit="minute" records={30} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
@ -14,14 +14,14 @@ export default function useFetch(url, params = {}, options = {}) {
|
||||
const keys = Object.keys(params)
|
||||
.sort()
|
||||
.map(key => params[key]);
|
||||
const { update = [], onDataLoad = () => {} } = options;
|
||||
const { update = [], onDataLoad = () => {}, disabled, headers } = options;
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
setLoadiing(true);
|
||||
setError(null);
|
||||
const time = performance.now();
|
||||
const { data, status } = await get(`${basePath}${url}`, params);
|
||||
const { data, status } = await get(`${basePath}${url}`, params, headers);
|
||||
|
||||
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
|
||||
|
||||
@ -43,7 +43,7 @@ export default function useFetch(url, params = {}, options = {}) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (url) {
|
||||
if (url && !disabled) {
|
||||
const { interval, delay = 0 } = options;
|
||||
|
||||
setTimeout(() => loadData(), delay);
|
||||
@ -54,7 +54,7 @@ export default function useFetch(url, params = {}, options = {}) {
|
||||
clearInterval(id);
|
||||
};
|
||||
}
|
||||
}, [url, ...keys, ...update]);
|
||||
}, [url, disabled, ...keys, ...update]);
|
||||
|
||||
return { data, status, error, loading };
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Vis detajler",
|
||||
"label.accounts": "Kontoer",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Godkendt adgangskode",
|
||||
"label.current-password": "Nuværende adgangskode",
|
||||
"label.custom-range": "Tilpasset interval",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Adgangskode",
|
||||
"label.passwords-dont-match": "Adgangskoder matcher ikke",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Påkrævet",
|
||||
"label.settings": "Indstillinger",
|
||||
"label.this-month": "Denne måned",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Details anzeigen",
|
||||
"label.accounts": "Konten",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Passwort wiederholen",
|
||||
"label.current-password": "Derzeitiges Passwort",
|
||||
"label.custom-range": "Benutzerdefinierter Bereich",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Passwort",
|
||||
"label.passwords-dont-match": "Passwörter stimmen nicht überein",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Erforderlich",
|
||||
"label.settings": "Einstellungen",
|
||||
"label.this-month": "Diesen Monat",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Λεπτομέρειες",
|
||||
"label.accounts": "Λογαριασμοί",
|
||||
"label.administrator": "Διαχειριστής",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Επιβεβαίωση κωδικού",
|
||||
"label.current-password": "Τωρινός κωδικός πρόσβασης",
|
||||
"label.custom-range": "Προσαρμοσμένο εύρος",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Κωδικός",
|
||||
"label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
|
||||
"label.profile": "Προφίλ",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Απαιτείται",
|
||||
"label.settings": "Ρυθμίσεις",
|
||||
"label.this-month": "Αυτο το μήνα",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "View details",
|
||||
"label.accounts": "Accounts",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Confirm password",
|
||||
"label.current-password": "Current password",
|
||||
"label.custom-range": "Custom range",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Password",
|
||||
"label.passwords-dont-match": "Passwords don't match",
|
||||
"label.profile": "Profile",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Required",
|
||||
"label.settings": "Settings",
|
||||
"label.this-month": "This month",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Ver detalles",
|
||||
"label.accounts": "Usuarios",
|
||||
"label.administrator": "Administrador",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Confirmar contraseña",
|
||||
"label.current-password": "Contraseña actual",
|
||||
"label.custom-range": "Custom range",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Contraseña",
|
||||
"label.passwords-dont-match": "Las contraseñas no coinciden",
|
||||
"label.profile": "Perfil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Requerido",
|
||||
"label.settings": "Configuraciones",
|
||||
"label.this-month": "Este mes",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Vís upplýsingar",
|
||||
"label.accounts": "Brúkarar",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Vátta loyniorð",
|
||||
"label.current-password": "Núverandi loyniorð",
|
||||
"label.custom-range": "Tillaga spenni",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Loyniorð",
|
||||
"label.passwords-dont-match": "Loyniorðini eru ikki eins",
|
||||
"label.profile": "Brúkari",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Krav",
|
||||
"label.settings": "Stillingar",
|
||||
"label.this-month": "Hendan mánan",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Voir les details",
|
||||
"label.accounts": "Comptes",
|
||||
"label.administrator": "Administrateur",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Confirmation du mot de passe",
|
||||
"label.current-password": "Mot de passe actuel",
|
||||
"label.custom-range": "Plage personnalisée",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Mot de passe",
|
||||
"label.passwords-dont-match": "Les mots de passe ne correspondent pas",
|
||||
"label.profile": "Profile",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Requis",
|
||||
"label.settings": "Paramètres",
|
||||
"label.this-month": "Ce mois ci",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Lihat Detil",
|
||||
"label.accounts": "Akun",
|
||||
"label.administrator": "Pengelola",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Konfirmasi kata sandi",
|
||||
"label.current-password": "Kata sandi sekarang",
|
||||
"label.custom-range": "Rentang khusus",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Kata sandi",
|
||||
"label.passwords-dont-match": "Kata sandi tidak cocok",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Wajib",
|
||||
"label.settings": "Pengaturan",
|
||||
"label.this-month": "Bulan ini",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "詳細を見る",
|
||||
"label.accounts": "アカウント",
|
||||
"label.administrator": "管理者",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "パスワード(確認)",
|
||||
"label.current-password": "現在のパスワード",
|
||||
"label.custom-range": "期間を指定する",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "パスワード",
|
||||
"label.passwords-dont-match": "パスワードが一致しません",
|
||||
"label.profile": "プロファイル",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "必須",
|
||||
"label.settings": "設定",
|
||||
"label.this-month": "今月",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Дэлгэрүүлж харах",
|
||||
"label.accounts": "Хэрэглэгчид",
|
||||
"label.administrator": "Админ",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Шинэ нууц үгээ давтах",
|
||||
"label.current-password": "Ашиглаж буй нууц үг",
|
||||
"label.custom-range": "Дурын хугацаа",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Нууц үг",
|
||||
"label.passwords-dont-match": "Нууц үг тохирохгүй байна",
|
||||
"label.profile": "Бүртгэл",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Шаардлагатай",
|
||||
"label.settings": "Тохиргоо",
|
||||
"label.this-month": "Энэ сар",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Vis detaljer",
|
||||
"label.accounts": "Kontoer",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Godkjenn passord",
|
||||
"label.current-password": "Nåværende passord",
|
||||
"label.custom-range": "Egendefinert utvalg",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Passord",
|
||||
"label.passwords-dont-match": "Passordene er ikke like",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Påkrevd",
|
||||
"label.settings": "Innstillinger",
|
||||
"label.this-month": "Denne måneden",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Meer details",
|
||||
"label.accounts": "Gebruikers",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Wachtwoord bevestigen",
|
||||
"label.current-password": "Huidig wachtwoord",
|
||||
"label.custom-range": "Aangepast bereik",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Wachtwoord",
|
||||
"label.passwords-dont-match": "Wachtwoorden komen niet overeen",
|
||||
"label.profile": "Profiel",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Verplicht",
|
||||
"label.settings": "Instellingen",
|
||||
"label.this-month": "Deze maand",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Ver detalhes",
|
||||
"label.accounts": "Contas",
|
||||
"label.administrator": "Administrador",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Confirmar palavra-passe",
|
||||
"label.current-password": "Palavra-passe atual",
|
||||
"label.custom-range": "Intervalo personalizado",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Palavra-passe",
|
||||
"label.passwords-dont-match": "Palavra-passes não correspondem",
|
||||
"label.profile": "Perfil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Obrigatório",
|
||||
"label.settings": "Definições",
|
||||
"label.this-month": "Este mês",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Vizualizare detalii",
|
||||
"label.accounts": "Conturi",
|
||||
"label.administrator": "Administrator",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Confirmare parolă",
|
||||
"label.current-password": "Parola curentă",
|
||||
"label.custom-range": "Interval personalizat",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Parolă",
|
||||
"label.passwords-dont-match": "Parolele nu se potrivesc",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Obligatoriu",
|
||||
"label.settings": "Setări",
|
||||
"label.this-month": "Această lună",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Посмотреть детали",
|
||||
"label.accounts": "Аккаунты",
|
||||
"label.administrator": "Администратор",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Подтвердить пароль",
|
||||
"label.current-password": "Текущий пароль",
|
||||
"label.custom-range": "Другой период",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Пароль",
|
||||
"label.passwords-dont-match": "Пароли не совпадают",
|
||||
"label.profile": "Профиль",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Обязательное",
|
||||
"label.settings": "Настройки",
|
||||
"label.this-month": "Этот месяц",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Visa detaljer",
|
||||
"label.accounts": "Konton",
|
||||
"label.administrator": "Administratör",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Bekräfta lösenord",
|
||||
"label.current-password": "Nuvarande lösenord",
|
||||
"label.custom-range": "Anpassat urval",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Lösenord",
|
||||
"label.passwords-dont-match": "Lösenorden är inte samma",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Krävs",
|
||||
"label.settings": "Inställningar",
|
||||
"label.this-month": "Denna månad",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Detayı incele",
|
||||
"label.accounts": "Hesaplar",
|
||||
"label.administrator": "Yönetici",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Parolayı onayla",
|
||||
"label.current-password": "Mevcut parola",
|
||||
"label.custom-range": "Özelleştirilmiş aralık",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Parola",
|
||||
"label.passwords-dont-match": "Parolalar uyuşmuyor",
|
||||
"label.profile": "Profil",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Zorunlu alan",
|
||||
"label.settings": "Ayarlar",
|
||||
"label.this-month": "Bu ay",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "Переглянути деталі",
|
||||
"label.accounts": "Облікові записи",
|
||||
"label.administrator": "Адміністратор",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "Підтвердити пароль",
|
||||
"label.current-password": "Поточний пароль",
|
||||
"label.custom-range": "Довільний період",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "Пароль",
|
||||
"label.passwords-dont-match": "Паролі не співпадають",
|
||||
"label.profile": "Профіль",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "Обов'язкове",
|
||||
"label.settings": "Налаштування",
|
||||
"label.this-month": "Поточний місяць",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"button.view-details": "查看更多",
|
||||
"label.accounts": "账户",
|
||||
"label.administrator": "管理员",
|
||||
"label.all-websites": "All websites",
|
||||
"label.confirm-password": "确认密码",
|
||||
"label.current-password": "目前密码",
|
||||
"label.custom-range": "自定义时间段",
|
||||
@ -36,6 +37,7 @@
|
||||
"label.password": "密码",
|
||||
"label.passwords-dont-match": "密码不一致",
|
||||
"label.profile": "个人资料",
|
||||
"label.realtime": "Realtime",
|
||||
"label.required": "必填",
|
||||
"label.settings": "设置",
|
||||
"label.this-month": "本月",
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
addYears,
|
||||
subHours,
|
||||
subDays,
|
||||
startOfMinute,
|
||||
startOfHour,
|
||||
startOfDay,
|
||||
startOfWeek,
|
||||
@ -17,6 +18,7 @@ import {
|
||||
endOfWeek,
|
||||
endOfMonth,
|
||||
endOfYear,
|
||||
differenceInMinutes,
|
||||
differenceInHours,
|
||||
differenceInCalendarDays,
|
||||
differenceInCalendarMonths,
|
||||
@ -114,6 +116,7 @@ export function getDateFromString(str) {
|
||||
}
|
||||
|
||||
const dateFuncs = {
|
||||
minute: [differenceInMinutes, addMinutes, startOfMinute],
|
||||
hour: [differenceInHours, addHours, startOfHour],
|
||||
day: [differenceInCalendarDays, addDays, startOfDay],
|
||||
month: [differenceInCalendarMonths, addMonths, startOfMonth],
|
||||
|
@ -166,16 +166,6 @@ export async function createSession(website_id, data) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSessionById(session_id) {
|
||||
return runQuery(
|
||||
prisma.session.findOne({
|
||||
where: {
|
||||
session_id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSessionByUuid(session_uuid) {
|
||||
return runQuery(
|
||||
prisma.session.findOne({
|
||||
@ -285,6 +275,57 @@ export async function createAccount(data) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSessions(websites, start_at) {
|
||||
return runQuery(
|
||||
prisma.session.findMany({
|
||||
where: {
|
||||
website: {
|
||||
website_id: {
|
||||
in: websites,
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
gte: start_at,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPageviews(websites, start_at) {
|
||||
return runQuery(
|
||||
prisma.pageview.findMany({
|
||||
where: {
|
||||
website: {
|
||||
website_id: {
|
||||
in: websites,
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
gte: start_at,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getEvents(websites, start_at) {
|
||||
return runQuery(
|
||||
prisma.event.findMany({
|
||||
where: {
|
||||
website: {
|
||||
website_id: {
|
||||
in: websites,
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
gte: start_at,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
|
||||
const params = [website_id, start_at, end_at];
|
||||
const { url } = filters;
|
||||
@ -425,7 +466,7 @@ export function getActiveVisitors(website_id) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getEvents(
|
||||
export function getEventMetrics(
|
||||
website_id,
|
||||
start_at,
|
||||
end_at,
|
||||
|
12
lib/web.js
12
lib/web.js
@ -19,13 +19,17 @@ export const apiRequest = (method, url, body, headers) =>
|
||||
return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
|
||||
});
|
||||
|
||||
export const get = (url, params) => apiRequest('get', `${url}${getQueryString(params)}`);
|
||||
export const get = (url, params, headers) =>
|
||||
apiRequest('get', `${url}${getQueryString(params)}`, undefined, headers);
|
||||
|
||||
export const del = (url, params) => apiRequest('delete', `${url}${getQueryString(params)}`);
|
||||
export const del = (url, params, headers) =>
|
||||
apiRequest('delete', `${url}${getQueryString(params)}`, undefined, headers);
|
||||
|
||||
export const post = (url, params) => apiRequest('post', url, JSON.stringify(params));
|
||||
export const post = (url, params, headers) =>
|
||||
apiRequest('post', url, JSON.stringify(params), headers);
|
||||
|
||||
export const put = (url, params) => apiRequest('put', url, JSON.stringify(params));
|
||||
export const put = (url, params, headers) =>
|
||||
apiRequest('put', url, JSON.stringify(params), headers);
|
||||
|
||||
export const hook = (_this, method, callback) => {
|
||||
const orig = _this[method];
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "umami",
|
||||
"version": "0.81.0",
|
||||
"version": "0.82.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
|
@ -1,19 +1,48 @@
|
||||
import { subMinutes } from 'date-fns';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, unauthorized, methodNotAllowed } from 'lib/response';
|
||||
import { ok, methodNotAllowed, badRequest } from 'lib/response';
|
||||
import { getEvents, getPageviews, getSessions, getUserWebsites } from 'lib/queries';
|
||||
import { createToken, parseToken } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
const { is_admin } = req.auth;
|
||||
|
||||
if (!is_admin) {
|
||||
return unauthorized(res);
|
||||
async function getData(websites, time) {
|
||||
return Promise.all([
|
||||
getPageviews(websites, time),
|
||||
getSessions(websites, time),
|
||||
getEvents(websites, time),
|
||||
]);
|
||||
}
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const [pageviews, sessions, events] = await Promise.all([[], [], []]);
|
||||
const { type } = req.query;
|
||||
const { user_id } = req.auth;
|
||||
|
||||
return ok(res, { pageviews, sessions, events });
|
||||
if (type === 'init') {
|
||||
const websites = await getUserWebsites(user_id);
|
||||
const ids = websites.map(({ website_id }) => website_id);
|
||||
const [pageviews, sessions, events] = await getData(ids, subMinutes(new Date(), 30));
|
||||
const token = await createToken({ websites: ids });
|
||||
|
||||
return ok(res, { websites, token, data: { pageviews, sessions, events } });
|
||||
}
|
||||
|
||||
if (type === 'update') {
|
||||
const token = req.headers['x-umami-token'];
|
||||
|
||||
if (!token) {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const { websites } = await parseToken(token);
|
||||
|
||||
const [pageviews, sessions, events] = await getData(websites, new Date());
|
||||
|
||||
return ok(res, { pageviews, sessions, events });
|
||||
}
|
||||
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { getEvents } from 'lib/queries';
|
||||
import { getEventMetrics } from 'lib/queries';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
@ -21,7 +21,7 @@ export default async (req, res) => {
|
||||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
||||
const events = await getEvents(websiteId, startDate, endDate, tz, unit, { url });
|
||||
const events = await getEventMetrics(websiteId, startDate, endDate, tz, unit, { url });
|
||||
|
||||
return ok(res, events);
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ export default async (req, res) => {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const [pageviews, uniques] = await Promise.all([
|
||||
const [pageviews, sessions] = await Promise.all([
|
||||
getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', url),
|
||||
getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url),
|
||||
]);
|
||||
|
||||
return ok(res, { pageviews, uniques });
|
||||
return ok(res, { pageviews, sessions });
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
|
Loading…
Reference in New Issue
Block a user