diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index 3d7b7001..ca93f391 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -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 }); diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js index 4f10e83e..24f642c1 100644 --- a/components/metrics/BarChart.js +++ b/components/metrics/BarChart.js @@ -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': diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js index 9120a6c3..d20db5eb 100644 --- a/components/metrics/PageviewsChart.js +++ b/components/metrics/PageviewsChart.js @@ -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, diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 6a07afe5..13d369c0 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -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 && } 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: , 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 ( -
Real time
+
+ +
- +
); } diff --git a/hooks/useFetch.js b/hooks/useFetch.js index 0eb82b13..b74a8fe7 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -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 }; } diff --git a/lang/da-DK.json b/lang/da-DK.json index cd96cb3f..ae8c4321 100644 --- a/lang/da-DK.json +++ b/lang/da-DK.json @@ -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", diff --git a/lang/de-DE.json b/lang/de-DE.json index 8d507693..fdc7c3a1 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -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", diff --git a/lang/el-GR.json b/lang/el-GR.json index 14cd69ca..ec4b3217 100644 --- a/lang/el-GR.json +++ b/lang/el-GR.json @@ -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": "Αυτο το μήνα", diff --git a/lang/en-US.json b/lang/en-US.json index 7c513b95..7251c123 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -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", diff --git a/lang/es-MX.json b/lang/es-MX.json index 16d911ea..9e6a6206 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -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", diff --git a/lang/fo-FO.json b/lang/fo-FO.json index db9e972b..2c379960 100644 --- a/lang/fo-FO.json +++ b/lang/fo-FO.json @@ -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", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index 762cae34..013f89e5 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -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", diff --git a/lang/id-ID.json b/lang/id-ID.json index 8099cd75..8b09c0a6 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -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", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index 51cdaec1..bbf73ed5 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -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": "今月", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index 694e068e..70af8f29 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -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": "Энэ сар", diff --git a/lang/nb-NO.json b/lang/nb-NO.json index dcd97d47..8e93b05c 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -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", diff --git a/lang/nl-NL.json b/lang/nl-NL.json index 54f7894f..8978d7ee 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -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", diff --git a/lang/pt-PT.json b/lang/pt-PT.json index db9a8a7f..971c1aa7 100644 --- a/lang/pt-PT.json +++ b/lang/pt-PT.json @@ -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", diff --git a/lang/ro-RO.json b/lang/ro-RO.json index edaf27d6..8e28f887 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -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ă", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 74985574..05c9e0da 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -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": "Этот месяц", diff --git a/lang/sv-SE.json b/lang/sv-SE.json index 9e5e5958..5a1662a5 100644 --- a/lang/sv-SE.json +++ b/lang/sv-SE.json @@ -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", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index 2e84051d..b1b206f3 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -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", diff --git a/lang/uk-UA.json b/lang/uk-UA.json index 9c5065bf..4d077847 100644 --- a/lang/uk-UA.json +++ b/lang/uk-UA.json @@ -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": "Поточний місяць", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index d5bf54cd..761ed17a 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -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": "本月", diff --git a/lib/date.js b/lib/date.js index cdfe322c..50d623bb 100644 --- a/lib/date.js +++ b/lib/date.js @@ -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], diff --git a/lib/queries.js b/lib/queries.js index 6312057f..ecf351b1 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -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, diff --git a/lib/web.js b/lib/web.js index aceb6498..24d1d982 100644 --- a/lib/web.js +++ b/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]; diff --git a/package.json b/package.json index 8f9edabb..22dd957b 100644 --- a/package.json +++ b/package.json @@ -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 ", "license": "MIT", diff --git a/pages/api/realtime.js b/pages/api/realtime.js index 7717c9b9..2130521b 100644 --- a/pages/api/realtime.js +++ b/pages/api/realtime.js @@ -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); diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js index 0498052f..7d98717a 100644 --- a/pages/api/website/[id]/events.js +++ b/pages/api/website/[id]/events.js @@ -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); } diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js index 07362359..965f28ae 100644 --- a/pages/api/website/[id]/pageviews.js +++ b/pages/api/website/[id]/pageviews.js @@ -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);