From 992908c72879cd4204697a348c14dc986a76bc06 Mon Sep 17 00:00:00 2001 From: Adam Coard Date: Tue, 29 Sep 2020 23:45:38 +0000 Subject: [PATCH 01/71] Added vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5db4c8fb..715ff703 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ .DS_Store .idea *.iml +.vscode/* # debug npm-debug.log* From dbbba5c5b102bedf86f5972bf09e9bba06fe829b Mon Sep 17 00:00:00 2001 From: RubenD Date: Thu, 1 Oct 2020 21:30:05 +0200 Subject: [PATCH 02/71] Added two missing german tanslations --- lang/de-DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/de-DE.json b/lang/de-DE.json index 7c45d131..8d507693 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -7,7 +7,7 @@ "button.copy-to-clipboard": "In die Zwischenablage kopieren", "button.date-range": "Datumsbereich", "button.delete": "Löschen", - "button.dismiss": "Dismiss", + "button.dismiss": "Verwerfen", "button.edit": "Bearbeiten", "button.login": "Anmelden", "button.more": "Mehr", @@ -55,7 +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.new-version-available": "Eine neue Version umami {version} ist verfügbar!", "message.no-data-available": "Keine Daten vorhanden.", "message.no-websites-configured": "Es ist keine Webseite vorhanden.", "message.page-not-found": "Seite nicht gefunden.", From bbc7e25ee8fcb040acdfc30c72de8f9db0675132 Mon Sep 17 00:00:00 2001 From: Adam Coard Date: Fri, 2 Oct 2020 00:05:10 +0000 Subject: [PATCH 03/71] Load testing added via `npm run loadtest` - npm scripts added - loadtesting "--weight" option for quick configs - Use chalk in loadtest for logging TODO: Need better way to pass in websiteId --- package.json | 6 +- scripts/loadtest.js | 140 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 scripts/loadtest.js diff --git a/package.json b/package.json index 92c8b34f..1ca2e62b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,10 @@ "merge-lang": "node scripts/merge-lang.js", "format-lang": "node scripts/format-lang.js", "compile-lang": "formatjs compile-folder --ast build lang-compiled", - "check-lang": "node scripts/check-lang.js" + "check-lang": "node scripts/check-lang.js", + "loadtest": "node scripts/loadtest.js", + "loadtest:medium": "node scripts/loadtest.js --weight=medium", + "loadtest:heavy": "node scripts/loadtest.js --weight=heavy --verbose" }, "lint-staged": { "**/*.js": [ @@ -107,6 +110,7 @@ "extract-react-intl-messages": "^4.1.1", "husky": "^4.3.0", "lint-staged": "^10.4.0", + "loadtest": "5.1.0", "npm-run-all": "^4.1.5", "postcss-flexbugs-fixes": "^4.2.1", "postcss-import": "^12.0.1", diff --git a/scripts/loadtest.js b/scripts/loadtest.js new file mode 100644 index 00000000..e2146dd0 --- /dev/null +++ b/scripts/loadtest.js @@ -0,0 +1,140 @@ +const loadtest = require('loadtest'); +const chalk = require('chalk'); +const trunc = num => +num.toFixed(1); + +/** + * Example invocations: + * + * npm run loadtest -- --weight=heavy + * npm run loadtest -- --weight=heavy --verbose + * npm run loadtest -- --weight=single --verbose + * npm run loadtest -- --weight=medium + */ + +/** + * Command line arguments like --weight=heavy and --verbose use this object + * If you are providing _alternative_ configs, use --weight + * e.g. add --weight=ultra then add commandlineOptions.ultra={} + * --verbose can be combied with any weight. + */ +const commandlineOptions = { + single: { + concurrency: 1, + requestsPerSecond: 1, + maxSeconds: 5, + maxRequests: 1, + }, + // Heavy can saturate CPU which leads to requests stalling depending on machine + // Keep an eye if --verbose logs pause, or if node CPU in top is > 100. + // https://github.com/alexfernandez/loadtest#usage-donts + heavy: { + concurrency: 10, + requestsPerSecond: 200, + maxSeconds: 60, + }, + // Throttled requests should not max out CPU, + medium: { + concurrency: 3, + requestsPerSecond: 5, + maxSeconds: 60, + }, + verbose: { statusCallback }, +}; + +const options = { + url: 'http://localhost:3000', + method: 'POST', + concurrency: 5, + requestsPerSecond: 5, + maxSeconds: 5, + requestGenerator: (params, options, client, callback) => { + const message = JSON.stringify(mockPageView()); + options.headers['Content-Length'] = message.length; + options.headers['Content-Type'] = 'application/json'; + options.headers['user-agent'] = 'User-Agent: Mozilla/5.0 LoadTest'; + options.body = message; + options.path = '/api/collect'; + const request = client(options, callback); + request.write(message); + return request; + }, +}; + +function getArgument() { + const weight = process.argv[2] && process.argv[2].replace('--weight=', ''); + const verbose = process.argv.includes('--verbose') && 'verbose'; + return [weight, verbose]; +} + +// Patch in all command line arguments over options object +// Must do this prior to calling `loadTest()` +getArgument().map(arg => Object.assign(options, commandlineOptions[arg])); + +loadtest.loadTest(options, (error, results) => { + if (error) { + return console.error(chalk.redBright('Got an error: %s', error)); + } + console.log(chalk.bold(chalk.yellow('\n--------\n'))); + console.log(chalk.yellowBright('Loadtests complete:'), chalk.greenBright('success'), '\n'); + prettyLogItem('Total Requests:', results.totalRequests); + prettyLogItem('Total Errors:', results.totalErrors); + + prettyLogItem( + 'Latency(mean/min/max)', + trunc(results.meanLatencyMs), + '/', + trunc(results.maxLatencyMs), + '/', + trunc(results.minLatencyMs), + ); + + if (results.totalErrors) { + console.log(chalk.redBright('*'), chalk.red('Total Errors:'), results.totalErrors); + } + + if (results.errorCodes && Object.keys(results.errorCodes).length) { + console.log(chalk.redBright('*'), chalk.red('Error Codes:'), results.errorCodes); + } + // console.log(results); +}); + +/** + * Create a new object for each request. Note, we could randomize values here if desired. + * + * TODO: Need a better way of passing in websiteId, hostname, URL. + * + * @param {object} payload pageview payload same as sent via tracker + */ +function mockPageView( + payload = { + website: 'fcd4c7e3-ed76-439c-9121-3a0f102df126', + hostname: 'localhost', + screen: '1680x1050', + url: '/LOADTESTING', + }, +) { + return { + type: 'pageview', + payload, + }; +} + +// If you pass in --verbose, this function is called +function statusCallback(error, result, latency) { + console.log( + chalk.yellowBright(`\n## req #${result.requestIndex + 1} of ${latency.totalRequests}`), + ); + prettyLogItem('Request elapsed milliseconds:', trunc(result.requestElapsed)); + prettyLogItem( + 'Latency(mean/max/min):', + trunc(latency.meanLatencyMs), + '/', + trunc(latency.maxLatencyMs), + '/', + trunc(latency.minLatencyMs), + ); +} + +function prettyLogItem(label, ...args) { + console.log(chalk.redBright('*'), chalk.green(label), ...args); +} From 77db711a6771f894e33f17ff80fdd17476aab29e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 1 Oct 2020 17:32:49 -0700 Subject: [PATCH 04/71] Updated empty message for websites. --- components/pages/WebsiteList.js | 8 +++----- lib/queries.js | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js index 0df24877..73d20c98 100644 --- a/components/pages/WebsiteList.js +++ b/components/pages/WebsiteList.js @@ -1,16 +1,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { useRouter } from 'next/router'; +import Link from 'components/common/Link'; import WebsiteChart from 'components/metrics/WebsiteChart'; import Page from 'components/layout/Page'; -import Button from 'components/common/Button'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import useFetch from 'hooks/useFetch'; import Arrow from 'assets/arrow-right.svg'; import styles from './WebsiteList.module.css'; export default function WebsiteList({ userId }) { - const router = useRouter(); const { data } = useFetch('/api/websites', { user_id: userId }); if (!data) { @@ -33,9 +31,9 @@ export default function WebsiteList({ userId }) { /> } > - + )} diff --git a/lib/queries.js b/lib/queries.js index d10777fd..7c0bb6e7 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -25,7 +25,7 @@ export async function runQuery(query) { }); } -export async function rawQuery(query, params) { +export async function rawQuery(query, params = []) { const db = getDatabase(); if (db !== POSTGRESQL && db !== MYSQL) { diff --git a/package.json b/package.json index 92c8b34f..066448ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.68.0", + "version": "0.69.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From ad9d285c2d7be8ddc4efb92decaebc0d2bf82abd Mon Sep 17 00:00:00 2001 From: rubjo Date: Fri, 2 Oct 2020 13:35:33 +0200 Subject: [PATCH 05/71] =?UTF-8?q?i18n:=20Added=20=F0=9F=87=B3=F0=9F=87=B4?= =?UTF-8?q?=20Norwegian=20Bokm=C3=A5l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/nb-NO.json | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lang/nb-NO.json diff --git a/lang/nb-NO.json b/lang/nb-NO.json new file mode 100644 index 00000000..3c39e0d6 --- /dev/null +++ b/lang/nb-NO.json @@ -0,0 +1,97 @@ +{ + "button.add-account": "Legg til konto", + "button.add-website": "Legg til nettsted", + "button.back": "Tilbake", + "button.cancel": "Avvis", + "button.change-password": "Bytt passord", + "button.copy-to-clipboard": "Kopier til utklippstavle", + "button.date-range": "Datointervall", + "button.delete": "Slett", + "button.dismiss": "Avbryt", + "button.edit": "Rediger", + "button.login": "Logg inn", + "button.more": "Mer", + "button.refresh": "Oppdater", + "button.reset": "Nullstill", + "button.save": "Lagre", + "button.single-day": "Enkelt dag", + "button.view-details": "Vis detaljer", + "label.accounts": "Kontoer", + "label.administrator": "Administrator", + "label.confirm-password": "Godkjenn passord", + "label.current-password": "Nåværende passord", + "label.custom-range": "Egendefinert utvalg", + "label.dashboard": "Dashboard", + "label.default-date-range": "Standard datoperiode", + "label.domain": "Domene", + "label.enable-share-url": "Aktiver delings-URL", + "label.invalid": "Ugyldig", + "label.invalid-domain": "Ugyldig domene", + "label.last-days": "Siste {x} dager", + "label.last-hours": "Siste {x} timer", + "label.logged-in-as": "Logget på som {brukernavn}", + "label.logout": "Logg ut", + "label.name": "Navn", + "label.new-password": "Nytt passord", + "label.password": "Passord", + "label.passwords-dont-match": "Passordene er ikke like", + "label.profile": "Profil", + "label.required": "Påkrevd", + "label.settings": "Innstillinger", + "label.this-month": "Denne måneden", + "label.this-week": "Denne uka", + "label.this-year": "I år", + "label.timezone": "Tidssone", + "label.today": "I dag", + "label.unknown": "Ukjent", + "label.username": "Brukernavn", + "label.websites": "Nettsteder", + "message.active-users": "{x} nåværende {x, flertall, en {besøkende} annen {besøkende}}", + "message.confirm-delete": "Er du sikker på at du vil slette {target}?", + "message.copied": "Kopiert!", + "message.delete-warning": "Alle tilknyttede data slettes også.", + "message.failure": "Noe gikk galt.", + "message.get-share-url": "Få delings-URL", + "message.get-tracking-code": "Få sporingskode", + "message.go-to-settings": "Gå til innstillinger", + "message.incorrect-username-password": "Ugyldig brukernavn/passord.", + "message.new-version-available": "En ny versjon av umami {version} er tilgjengelig!", + "message.no-data-available": "Ingen data tilgjengelig.", + "message.no-websites-configured": "Du har ikke satt opp noen nettsteder.", + "message.page-not-found": "Side ikke funnet.", + "message.powered-by": "Drevet av {name}", + "message.save-success": "Lagret!", + "message.share-url": "Dette er den offentlige delings-URL-en for {target}.", + "message.track-stats": "For å spore statistikk for {target}, plasser følgende kode i {head}-delen av nettstedet ditt.", + "message.type-delete": "Skriv inn {delete} i boksen nedenfor for å bekrefte.", + "metrics.actions": "Handlinger", + "metrics.average-visit-time": "Gjennomsnittlig besøkelsestid", + "metrics.bounce-rate": "Avvisningsfrekvens", + "metrics.browsers": "Nettlesere", + "metrics.countries": "Land", + "metrics.device.desktop": "Desktop", + "metrics.device.laptop": "Laptop", + "metrics.device.mobile": "Mobiltelefon", + "metrics.device.tablet": "Nettbrett", + "metrics.devices": "Enheter", + "metrics.events": "Arrangementer", + "metrics.filter.combined": "Kombinert", + "metrics.filter.domain-only": "Bare domene", + "metrics.filter.raw": "Rå", + "metrics.operating-systems": "Operativsystemer", + "metrics.page-views": "Sidevisninger", + "metrics.pages": "Sider", + "metrics.referrers": "Referanser", + "metrics.unique-visitors": "Unike besøkende", + "metrics.views": "Visninger", + "metrics.visitors": "Besøkende", + "title.add-account": "Legg til konto", + "title.add-website": "Legg til nettsted", + "title.change-password": "Bytt passord", + "title.delete-account": "Slett konto", + "title.delete-website": "Slett nettstedet", + "title.edit-account": "Rediger konto", + "title.edit-website": "Rediger nettsted", + "title.share-url": "Del URL", + "title.tracking-code": "Sporingskode" +} From 8d493f6b83f69388b75560a073b133722037f0e9 Mon Sep 17 00:00:00 2001 From: rubjo Date: Fri, 2 Oct 2020 13:48:22 +0200 Subject: [PATCH 06/71] Fix active users translation --- lang/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/nb-NO.json b/lang/nb-NO.json index 3c39e0d6..e7dbac17 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -46,7 +46,7 @@ "label.unknown": "Ukjent", "label.username": "Brukernavn", "label.websites": "Nettsteder", - "message.active-users": "{x} nåværende {x, flertall, en {besøkende} annen {besøkende}}", + "message.active-users": "{x} nåværende {x, plural, en {besøkende} andre {besøkende}}", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", "message.copied": "Kopiert!", "message.delete-warning": "Alle tilknyttede data slettes også.", From 60613c5cbf11c7bb55e67a5fe636dfe33680dcb7 Mon Sep 17 00:00:00 2001 From: rubjo Date: Fri, 2 Oct 2020 13:48:55 +0200 Subject: [PATCH 07/71] =?UTF-8?q?Add=20Norwegian=20Bokm=C3=A5l=20to=20list?= =?UTF-8?q?=20of=20languages=20to=20select?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lang.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/lang.js b/lib/lang.js index 02dd5141..c8c73854 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -1,5 +1,5 @@ import { format } from 'date-fns'; -import { enUS, nl, zhCN, tr, ru, de, ja, es, fr, da, sv, el, pt, ro } from 'date-fns/locale'; +import { enUS, nl, zhCN, tr, ru, de, ja, es, fr, da, sv, el, pt, ro, nb } from 'date-fns/locale'; import enMessages from 'lang-compiled/en-US.json'; import nlMessages from 'lang-compiled/nl-NL.json'; import zhCNMessages from 'lang-compiled/zh-CN.json'; @@ -16,6 +16,7 @@ import grMessages from 'lang-compiled/el-GR.json'; import foMessages from 'lang-compiled/fo-FO.json'; import ptMessages from 'lang-compiled/pt-PT.json'; import roMessages from 'lang-compiled/ro-RO.json'; +import nbNOMessages from 'lang-compiled/nb-NO.json'; export const messages = { 'en-US': enMessages, @@ -34,6 +35,7 @@ export const messages = { 'fo-FO': foMessages, 'pt-PT': ptMessages, 'ro-RO': roMessages, + 'nb-NO': nbNOMessages, }; export const dateLocales = { @@ -53,6 +55,7 @@ export const dateLocales = { 'fo-FO': da, 'pt-PT': pt, 'ro-RO': ro, + 'nb-NO': nb, }; export const menuOptions = [ @@ -72,6 +75,7 @@ export const menuOptions = [ { label: 'Română', value: 'ro-RO', display: 'ro' }, { label: 'Svenska', value: 'sv-SE', display: 'sv' }, { label: 'Türkçe', value: 'tr-TR', display: 'tr' }, + { label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' }, ]; export function dateFormat(date, str, locale) { From 9edf657e253d4ec681b22272a791821a5ebb2bc7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 10:25:31 -0700 Subject: [PATCH 08/71] Update version check. Updated packages. --- components/common/UpdateNotice.js | 2 +- components/layout/Footer.js | 5 +- hooks/useVersion.js | 17 ++-- package.json | 10 +-- redux/actions/app.js | 7 +- scripts/start-env.js | 2 +- yarn.lock | 140 ++++++++++++++++++------------ 7 files changed, 106 insertions(+), 77 deletions(-) diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js index 27ec562a..0bdb1041 100644 --- a/components/common/UpdateNotice.js +++ b/components/common/UpdateNotice.js @@ -8,7 +8,7 @@ import useForceUpdate from '../../hooks/useForceUpdate'; export default function UpdateNotice() { const forceUpdte = useForceUpdate(); - const { hasUpdate, latest, updateCheck } = useVersion(); + const { hasUpdate, latest, updateCheck } = useVersion(true); function handleViewClick() { location.href = 'https://github.com/mikecao/umami/releases'; diff --git a/components/layout/Footer.js b/components/layout/Footer.js index 7bd1ebd3..30782de0 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -2,9 +2,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import Link from 'components/common/Link'; import styles from './Footer.module.css'; +import useVersion from 'hooks/useVersion'; export default function Footer() { - const version = process.env.VERSION; + const { current } = useVersion(); return (
@@ -22,7 +23,7 @@ export default function Footer() { }} />
-
{`v${version}`}
+
{`v${current}`}
); diff --git a/hooks/useVersion.js b/hooks/useVersion.js index d8e3d699..bec5670b 100644 --- a/hooks/useVersion.js +++ b/hooks/useVersion.js @@ -1,27 +1,22 @@ 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'; +import { setItem } from 'lib/web'; -export default function useVersion() { +export default function useVersion(check) { 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() }); + setItem(VERSION_CHECK, { version: versions.latest, time: Date.now() }); }, [versions]); useEffect(() => { - if (!versions.latest) { + if (check && !versions.latest) { dispatch(checkVersion()); } - }, [versions]); + }, [versions, check]); - return { ...versions, hasUpdate, updateCheck }; + return { ...versions, updateCheck }; } diff --git a/package.json b/package.json index 066448ab..7d7b135d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.69.0", + "version": "0.70.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -63,7 +63,8 @@ "date-fns": "^2.16.1", "date-fns-tz": "^1.0.10", "detect-browser": "^5.1.1", - "formik": "^2.1.6", + "dotenv": "^8.2.0", + "formik": "^2.1.7", "immer": "^7.0.9", "is-localhost-ip": "^1.4.0", "isbot-fast": "^1.2.0", @@ -73,7 +74,7 @@ "next": "^9.5.3", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-intl": "^5.8.3", + "react-intl": "^5.8.4", "react-redux": "^7.2.1", "react-simple-maps": "^2.1.2", "react-spring": "^8.0.27", @@ -89,7 +90,7 @@ "uuid": "^8.3.0" }, "devDependencies": { - "@formatjs/cli": "^2.12.0", + "@formatjs/cli": "^2.13.0", "@prisma/cli": "2.8.0", "@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-node-resolve": "^9.0.0", @@ -97,7 +98,6 @@ "@svgr/webpack": "^5.4.0", "cross-env": "^7.0.2", "del": "^6.0.0", - "dotenv": "^8.2.0", "dotenv-cli": "^4.0.0", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", diff --git a/redux/actions/app.js b/redux/actions/app.js index 490bd7f1..94b77daa 100644 --- a/redux/actions/app.js +++ b/redux/actions/app.js @@ -1,6 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { getItem } from 'lib/web'; -import { LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants'; +import { LOCALE_CONFIG, THEME_CONFIG, VERSION_CHECK } from 'lib/constants'; +import semver from 'semver'; const app = createSlice({ name: 'app', @@ -10,6 +11,7 @@ const app = createSlice({ versions: { current: process.env.VERSION, latest: null, + hasUpdate: false, }, }, reducers: { @@ -60,11 +62,14 @@ export function checkVersion() { const { tag_name } = data; const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name; + const lastCheck = getItem(VERSION_CHECK); + const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest; return dispatch( setVersions({ current, latest, + hasUpdate, }), ); }; diff --git a/scripts/start-env.js b/scripts/start-env.js index 63b66369..05823b7a 100644 --- a/scripts/start-env.js +++ b/scripts/start-env.js @@ -1,3 +1,3 @@ const cli = require('next/dist/cli/next-start'); -cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || '0.0.0.0']); +cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || 'localhost']); diff --git a/yarn.lock b/yarn.lock index 00d976eb..3d341f16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,12 +1064,12 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@formatjs/cli@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-2.12.0.tgz#f0bb253db073903634e57e587e0395cd0d0cd681" - integrity sha512-F0epNBWCXjKGgej8GL1q4RLGqR38bRCPmGLb3VautkbZ74achB0cVGj2w/AdlQiJJ1mU5rEU13pRroukUBZ+GA== +"@formatjs/cli@^2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-2.13.0.tgz#64018c82f9fbc291918792daf1eed16e4b56bb7f" + integrity sha512-1E/rdrKiHi7gbcN0WuY792X+jCHWI+9jC3565hhZXAJYmgNe9SntRW02nEJOK3WOkhm6yMWiHPLkFpyLq5hW3w== dependencies: - "@formatjs/ts-transformer" "^2.11.0" + "@formatjs/ts-transformer" "^2.11.1" "@types/json-stable-stringify" "^1.0.32" "@types/lodash" "^4.14.150" "@types/loud-rejection" "^2.0.0" @@ -1078,10 +1078,11 @@ commander "^6.1.0" fast-glob "^3.2.4" fs-extra "^9.0.0" - intl-messageformat-parser "^6.0.7" + intl-messageformat-parser "^6.0.8" json-stable-stringify "^1.0.1" lodash "^4.17.15" loud-rejection "^2.2.0" + tslib "^2.0.1" typescript "^4.0" "@formatjs/ecma402-abstract@^1.2.2": @@ -1089,19 +1090,28 @@ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.2.tgz#4810bdbd696d3805c535fd0620b7c8f45ab3164f" integrity sha512-mLCoAPGlXCVskb/ojBO6iurGqwo6sZvAl8pRC4N25bz4LPWExAM9LsOo057zN3Br1JxUM3RZHG4YGnVt+nSRYQ== -"@formatjs/intl-displaynames@^3.3.9": - version "3.3.9" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.9.tgz#18eeb39aa05d4a8b064e520725f9178d0c3f8c50" - integrity sha512-6Ez9Ab9p9bsxCM4OlqsT+R0rmrj5lr6xjIXiCTs/pSDFeiNPQabWDHcBpiGlMRE3zifOwUOFSoi5AGGYMFgetw== +"@formatjs/ecma402-abstract@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.3.tgz#ca94911dd8e9c89eeaabba0f00e2f692979fc27b" + integrity sha512-sEkxTZj7qa+Pi/c4ppE5mxZYJkqQO3GNZzJZjAxgsXip2ixO/TZn58qrxdQ0V8mXmf+5xf+AfveyPvv4yHaRtw== dependencies: - "@formatjs/ecma402-abstract" "^1.2.2" + tslib "^2.0.1" -"@formatjs/intl-listformat@^4.2.7": - version "4.2.7" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.7.tgz#bcccfee92c69e661d4da51567540aa39fef9ceae" - integrity sha512-7sYy7pBGzClvSZI98FhVeNt6N/ELdvrj8pvLOfcR0+FQyhv0ixanog7bRo9kT8ECin9+RwEpTmX7jGCh8Bcgjw== +"@formatjs/intl-displaynames@^3.3.10": + version "3.3.10" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.10.tgz#905ad86431fdadfab2ec188bf9f4fe9e359d1fe6" + integrity sha512-SdIMuaKUO0N5zQb6CXtIrwjJbX+DC8ju7ifrcqpLagUMh2nIEJCz7sf0Q6lOMWEE+un1VTmjaXpRPP55cP40IA== dependencies: - "@formatjs/ecma402-abstract" "^1.2.2" + "@formatjs/ecma402-abstract" "^1.2.3" + tslib "^2.0.1" + +"@formatjs/intl-listformat@^4.2.8": + version "4.2.8" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.8.tgz#aa8935234dd5f8fcec6d08dfdf2be43e11ba671c" + integrity sha512-9qAThh/1HV9T/g6E11VbN5b209zg28fMUMrZqrpHiZZxc2PPHvP/CGqK7mo8hpyCoMUVo3kFxB5CFnw5difJrA== + dependencies: + "@formatjs/ecma402-abstract" "^1.2.3" + tslib "^2.0.1" "@formatjs/intl-numberformat@^5.5.2": version "5.6.2" @@ -1110,32 +1120,35 @@ dependencies: "@formatjs/ecma402-abstract" "^1.2.2" -"@formatjs/intl-relativetimeformat@^7.2.7": - version "7.2.7" - resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.7.tgz#dce72ebafb1ca0bf14673fab3b1aad145e1abac6" - integrity sha512-R6rxX4PfPQ/EuDRALLSIEDCDW/FAmmu6xpRXIPdZ33bEP7SXEksJB039Bw//I0VIm2fBeIlxe9oqvRGfuqpwVg== +"@formatjs/intl-relativetimeformat@^7.2.8": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.8.tgz#a423ef9acd379980f58730b4757713156076de14" + integrity sha512-h6H5lcPn1LbVlAk62m7DYtY68aE6AiZVK1bLEo3HeWrMBFCskWAe9I/5kI+RjStdGZzo+CqBl+rSTcrSXjVj+g== dependencies: - "@formatjs/ecma402-abstract" "^1.2.2" + "@formatjs/ecma402-abstract" "^1.2.3" + tslib "^2.0.1" -"@formatjs/intl@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.3.tgz#d59997b7ef832e7c47e8ca50861ff7ff7461d56c" - integrity sha512-gFuCIZEH6o1O2ZF8YlhHJEApRrBarQ7iyqxFp4ujllr/tcjgSxfzF+LSBCPyJ1OQIU98ynOc0XKdrAR2wUd3ow== +"@formatjs/intl@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.4.tgz#66441b85986726256f83fdffdb533c6334bc299b" + integrity sha512-aq6bhi2aZPYUEL15iiBrsNzDtw4Qe1r9dsqM26fbTbfWa6r5sdqcFwGySoeTzguxd+ZXoc9RypSMERjH92xFKA== dependencies: - "@formatjs/ecma402-abstract" "^1.2.2" - "@formatjs/intl-displaynames" "^3.3.9" - "@formatjs/intl-listformat" "^4.2.7" - "@formatjs/intl-relativetimeformat" "^7.2.7" + "@formatjs/ecma402-abstract" "^1.2.3" + "@formatjs/intl-displaynames" "^3.3.10" + "@formatjs/intl-listformat" "^4.2.8" + "@formatjs/intl-relativetimeformat" "^7.2.8" fast-memoize "^2.5.2" - intl-messageformat "^9.3.8" - intl-messageformat-parser "^6.0.7" + intl-messageformat "^9.3.9" + intl-messageformat-parser "^6.0.8" + tslib "^2.0.1" -"@formatjs/ts-transformer@^2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.11.0.tgz#90c4b5afae55fd8b8c7ab6aa08ca94a123eb94b9" - integrity sha512-d0++zpEeeCtE+RwbEB+TYw0WnC+jlNniIZu9NcILdgN6LEr9+TRxO+Gz4d7nj3g0D5X1LyNx6P4JI+byGxHqzw== +"@formatjs/ts-transformer@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.11.1.tgz#9b30c066cd1ca1831bfc76e22b01e2858b931923" + integrity sha512-VAjFBnWSQfO71PrR0NeGgwGoHOAlNMuQv4kdV6GpxQ/3d4YM+202Cpu6r1BmCvxkuXhijTMUu7ubKeenUr8WcA== dependencies: - intl-messageformat-parser "^6.0.7" + intl-messageformat-parser "^6.0.8" + tslib "^2.0.1" typescript "^4.0" "@formatjs/ts-transformer@^2.6.0": @@ -4145,10 +4158,10 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" -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== +formik@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.7.tgz#40bd04e59b242176d0a17c701830f1536cd7506b" + integrity sha512-n1wviIh0JsvHqj9PufNvOV+fS7mFwh9FfMxxTMnTrKR/uVYMS06DKaivXBlJdDF0qEwTcPHxSmIQ3deFHL3Hsg== dependencies: deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" @@ -4694,13 +4707,22 @@ intl-messageformat-parser@^6.0.7: dependencies: "@formatjs/ecma402-abstract" "^1.2.2" -intl-messageformat@^9.3.8: - version "9.3.8" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.8.tgz#561f31800cc3ced5dada6c991a0dd0233931850f" - integrity sha512-XuFoC6kvsgL1qtzro9ubOaJ2zVgeJWb5X0mTYvG7p1OinbOZYPscP8eYyVJf9g++tDy/fwx9TfeaFlunmhC+Vw== +intl-messageformat-parser@^6.0.8: + version "6.0.8" + resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.8.tgz#4180b280d21653df7c8c078e75e0bb7f0e3322c3" + integrity sha512-g1nV8YVI/Nscbu3qjGGgMcq61Es7L2bI+08gcbAx3taiFMJ3oJgQhC/wYksWLsq2cvLxq5pQ5Te06CE793/iVA== + dependencies: + "@formatjs/ecma402-abstract" "^1.2.3" + tslib "^2.0.1" + +intl-messageformat@^9.3.9: + version "9.3.9" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.9.tgz#41f9f139adbbb509da657133047c8c5bf4ca22a9" + integrity sha512-SB6b68bY+RZPBhg3XTmwfX0lL3ywvOuAcS+iH6cptiHPfzOPSzP05F3ZOirARwj8pVbC9Xd4w0pMtF/sGnHurw== dependencies: fast-memoize "^2.5.2" - intl-messageformat-parser "^6.0.7" + intl-messageformat-parser "^6.0.8" + tslib "^2.0.1" invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" @@ -7165,22 +7187,23 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-intl@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.3.tgz#2018aca11a49d2d1b0f602e0dda653a47bf59dfb" - integrity sha512-ueM7JhbBIi+6FpH6jCrJuKcYpjmFn9UAHA28ojY8LMAL1PowZ/53XGGMvfj32J0/2EIuHQ6vUbi/07kJqe1ksQ== +react-intl@^5.8.4: + version "5.8.4" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.4.tgz#aba5432fcba17f47d9d46ac9bea1881c92f4f354" + integrity sha512-ToDeHYBpO9WBAOpnwQKihUdxB4qE1nqcGKV9Jq2upj1zspxeX3OddWaZwz8wNV5yjoYnoDY3HLWeBP4IXwbwqg== dependencies: - "@formatjs/ecma402-abstract" "^1.2.2" - "@formatjs/intl" "^1.3.3" - "@formatjs/intl-displaynames" "^3.3.9" - "@formatjs/intl-listformat" "^4.2.7" - "@formatjs/intl-relativetimeformat" "^7.2.7" + "@formatjs/ecma402-abstract" "^1.2.3" + "@formatjs/intl" "^1.3.4" + "@formatjs/intl-displaynames" "^3.3.10" + "@formatjs/intl-listformat" "^4.2.8" + "@formatjs/intl-relativetimeformat" "^7.2.8" "@types/hoist-non-react-statics" "^3.3.1" fast-memoize "^2.5.2" hoist-non-react-statics "^3.3.2" - intl-messageformat "^9.3.8" - intl-messageformat-parser "^6.0.7" + intl-messageformat "^9.3.9" + intl-messageformat-parser "^6.0.8" shallow-equal "^1.2.1" + tslib "^2.0.1" react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0: version "16.13.1" @@ -8711,6 +8734,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" + integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" From 48a199421906219e6f873e279459b283542e3cee Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 10:34:22 -0700 Subject: [PATCH 09/71] Fix version check. --- components/common/UpdateNotice.js | 10 +++++----- hooks/useVersion.js | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js index 0bdb1041..7565d153 100644 --- a/components/common/UpdateNotice.js +++ b/components/common/UpdateNotice.js @@ -7,21 +7,21 @@ import Button from './Button'; import useForceUpdate from '../../hooks/useForceUpdate'; export default function UpdateNotice() { - const forceUpdte = useForceUpdate(); - const { hasUpdate, latest, updateCheck } = useVersion(true); + const forceUpdate = useForceUpdate(); + const { hasUpdate, checked, latest, updateCheck } = useVersion(true); function handleViewClick() { location.href = 'https://github.com/mikecao/umami/releases'; updateCheck(); - forceUpdte(); + forceUpdate(); } function handleDismissClick() { updateCheck(); - forceUpdte(); + forceUpdate(); } - if (!hasUpdate) { + if (!hasUpdate || checked) { return null; } diff --git a/hooks/useVersion.js b/hooks/useVersion.js index bec5670b..79b97a34 100644 --- a/hooks/useVersion.js +++ b/hooks/useVersion.js @@ -2,11 +2,12 @@ import { useEffect, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { checkVersion } from 'redux/actions/app'; import { VERSION_CHECK } from 'lib/constants'; -import { setItem } from 'lib/web'; +import { getItem, setItem } from 'lib/web'; export default function useVersion(check) { const dispatch = useDispatch(); const versions = useSelector(state => state.app.versions); + const checked = versions.latest === getItem(VERSION_CHECK)?.version; const updateCheck = useCallback(() => { setItem(VERSION_CHECK, { version: versions.latest, time: Date.now() }); @@ -18,5 +19,5 @@ export default function useVersion(check) { } }, [versions, check]); - return { ...versions, updateCheck }; + return { ...versions, checked, updateCheck }; } From 4d44737aeba86ca89d29fa30caeb24382b2fdf7a Mon Sep 17 00:00:00 2001 From: rubjo Date: Fri, 2 Oct 2020 19:34:27 +0200 Subject: [PATCH 10/71] Correct translation formatting --- lang/nb-NO.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/nb-NO.json b/lang/nb-NO.json index e7dbac17..a0ef3414 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -46,7 +46,7 @@ "label.unknown": "Ukjent", "label.username": "Brukernavn", "label.websites": "Nettsteder", - "message.active-users": "{x} nåværende {x, plural, en {besøkende} andre {besøkende}}", + "message.active-users": "{x} {x, plural, en {besøkende} andre {besøkende}} nå", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", "message.copied": "Kopiert!", "message.delete-warning": "Alle tilknyttede data slettes også.", From dd651e3c773cf9b56c2a4f000caa56a42f34144f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 11:41:29 -0700 Subject: [PATCH 11/71] Added Norwegian language. --- lang/nb-NO.json | 2 +- lib/lang.js | 2 +- public/country/nb-NO.json | 1 + yarn.lock | 113 +++++++++++++++++++++++++++++++++++++- 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 public/country/nb-NO.json diff --git a/lang/nb-NO.json b/lang/nb-NO.json index a0ef3414..dcd97d47 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -46,7 +46,7 @@ "label.unknown": "Ukjent", "label.username": "Brukernavn", "label.websites": "Nettsteder", - "message.active-users": "{x} {x, plural, en {besøkende} andre {besøkende}} nå", + "message.active-users": "{x} {x, plural, one {besøkende} other {besøkende}} nå", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", "message.copied": "Kopiert!", "message.delete-warning": "Alle tilknyttede data slettes også.", diff --git a/lib/lang.js b/lib/lang.js index c8c73854..a6d311b5 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -70,12 +70,12 @@ export const menuOptions = [ { label: '日本語', value: 'ja-JP', display: 'ja' }, { label: 'Монгол', value: 'mn-MN', display: 'mn' }, { label: 'Nederlands', value: 'nl-NL', display: 'nl' }, + { label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' }, { label: 'Português', value: 'pt-PT', display: 'pt' }, { label: 'Русский', value: 'ru-RU', display: 'ru' }, { label: 'Română', value: 'ro-RO', display: 'ro' }, { label: 'Svenska', value: 'sv-SE', display: 'sv' }, { label: 'Türkçe', value: 'tr-TR', display: 'tr' }, - { label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' }, ]; export function dateFormat(date, str, locale) { diff --git a/public/country/nb-NO.json b/public/country/nb-NO.json new file mode 100644 index 00000000..89449329 --- /dev/null +++ b/public/country/nb-NO.json @@ -0,0 +1 @@ +{"AF":"Afghanistan","AL":"Albania","DZ":"Algerie","AS":"Amerikansk Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua og Barbuda","AR":"Argentina","AM":"Armenia","AW":"Aruba","AZ":"Aserbajdsjan","AU":"Australia","BS":"Bahamas","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia-Hercegovina","BW":"Botswana","BV":"Bouvet\u00f8ya","BR":"Brasil","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","CA":"Canada","KY":"Cayman\u00f8yene","CL":"Chile","CX":"Christmas\u00f8ya","CO":"Colombia","CK":"Cook\u00f8yene","CR":"Costa Rica","CU":"Cuba","CW":"Cura\u00e7ao","DK":"Danmark","VI":"De amerikanske jomfru\u00f8yene","VG":"De britiske jomfru\u00f8yene","AE":"De forente arabiske emirater","TF":"De franske s\u00f8rterritorier","DO":"Den dominikanske republikk","CF":"Den sentralafrikanske republikk","IO":"Det britiske territoriet i Indiahavet","PS":"Det palestinske omr\u00e5det","DJ":"Djibouti","DM":"Dominica","EC":"Ecuador","EG":"Egypt","GQ":"Ekvatorial-Guinea","SV":"El Salvador","CI":"Elfenbenskysten","ER":"Eritrea","EE":"Estland","SZ":"Eswatini","ET":"Etiopia","FK":"Falklands\u00f8yene","FJ":"Fiji","PH":"Filippinene","FI":"Finland","FR":"Frankrike","GF":"Fransk Guyana","PF":"Fransk Polynesia","FO":"F\u00e6r\u00f8yene","GA":"Gabon","GM":"Gambia","GE":"Georgia","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Gr\u00f8nland","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard- og McDonald\u00f8yene","GR":"Hellas","HN":"Honduras","HK":"Hongkong S.A.R. Kina","BY":"Hviterussland","IN":"India","ID":"Indonesia","IQ":"Irak","IR":"Iran","IE":"Irland","IS":"Island","IL":"Israel","IT":"Italia","JM":"Jamaica","JP":"Japan","YE":"Jemen","JE":"Jersey","JO":"Jordan","KH":"Kambodsja","CM":"Kamerun","CV":"Kapp Verde","BQ":"Karibisk Nederland","KZ":"Kasakhstan","KE":"Kenya","CN":"Kina","KG":"Kirgisistan","KI":"Kiribati","CC":"Kokos\u00f8yene","KM":"Komorene","CG":"Kongo-Brazzaville","CD":"Kongo-Kinshasa","HR":"Kroatia","KW":"Kuwait","CY":"Kypros","LA":"Laos","LV":"Latvia","LS":"Lesotho","LB":"Libanon","LR":"Liberia","LY":"Libya","LI":"Liechtenstein","LT":"Litauen","LU":"Luxemburg","MO":"Macao S.A.R. Kina","MG":"Madagaskar","MW":"Malawi","MY":"Malaysia","MV":"Maldivene","ML":"Mali","MT":"Malta","IM":"Man","MA":"Marokko","MH":"Marshall\u00f8yene","MQ":"Martinique","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Mexico","FM":"Mikronesiaf\u00f8derasjonen","MD":"Moldova","MC":"Monaco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mosambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NL":"Nederland","NP":"Nepal","NZ":"New Zealand","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","KP":"Nord-Korea","MK":"Nord-Makedonia","MP":"Nord-Marianene","NF":"Norfolk\u00f8ya","NO":"Norge","NC":"Ny-Caledonia","OM":"Oman","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Ny-Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn\u00f8yene","PL":"Polen","PT":"Portugal","PR":"Puerto Rico","QA":"Qatar","RE":"R\u00e9union","RO":"Romania","RU":"Russland","RW":"Rwanda","KN":"Saint Kitts og Nevis","BL":"Saint-Barth\u00e9lemy","MF":"Saint-Martin","PM":"Saint-Pierre-et-Miquelon","SB":"Salomon\u00f8yene","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 og Pr\u00edncipe","SA":"Saudi-Arabia","SN":"Senegal","RS":"Serbia","SC":"Seychellene","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","ES":"Spania","LK":"Sri Lanka","SH":"St. Helena","LC":"St. Lucia","VC":"St. Vincent og Grenadinene","GB":"Storbritannia","SD":"Sudan","SR":"Surinam","SJ":"Svalbard og Jan Mayen","CH":"Sveits","SE":"Sverige","SY":"Syria","ZA":"S\u00f8r-Afrika","GS":"S\u00f8r-Georgia og S\u00f8r-Sandwich\u00f8yene","KR":"S\u00f8r-Korea","SS":"S\u00f8r-Sudan","TJ":"Tadsjikistan","TW":"Taiwan","TZ":"Tanzania","TH":"Thailand","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad og Tobago","TD":"Tsjad","CZ":"Tsjekkia","TN":"Tunisia","TM":"Turkmenistan","TC":"Turks- og Caicos\u00f8yene","TV":"Tuvalu","TR":"Tyrkia","DE":"Tyskland","UG":"Uganda","UA":"Ukraina","HU":"Ungarn","UY":"Uruguay","US":"USA","UM":"USAs ytre \u00f8yer","UZ":"Usbekistan","VU":"Vanuatu","VA":"Vatikanstaten","VE":"Venezuela","EH":"Vest-Sahara","VN":"Vietnam","WF":"Wallis og Futuna","ZM":"Zambia","ZW":"Zimbabwe","TL":"\u00d8st-Timor","AT":"\u00d8sterrike","AX":"\u00c5land"} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3d341f16..c38489ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1786,6 +1786,18 @@ agent-base@6: dependencies: debug "4" +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + +agentkeepalive@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz#c5d1bd4b129008f1163f236f86e5faea2026e2ef" + integrity sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8= + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -2322,6 +2334,13 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +bufferutil@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" + integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA== + dependencies: + node-gyp-build "~3.7.0" + builtin-modules@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" @@ -2759,6 +2778,14 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" +confinode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/confinode/-/confinode-2.1.1.tgz#6831961ca48fb3c9f7d5ff063022e2bc40bea86e" + integrity sha512-u5u0ZHpYMnVWtelxjalNtLvL+SdP7B/7s0JTFUIkyvqqIf67DAvy6SKaE6WZiwbufLPk+6zJKsh5SdpbtbFi9g== + dependencies: + quick-lru "^5.0.0" + yaml "^1.7.2" + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -3209,7 +3236,7 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3623,6 +3650,18 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + es6-symbol@^3.1.1, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -4537,6 +4576,14 @@ https-proxy-agent@5.0.0: agent-base "6" debug "4" +https-proxy-agent@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -5318,6 +5365,19 @@ loader-utils@^1.2.3: emojis-list "^3.0.0" json5 "^1.0.1" +loadtest@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/loadtest/-/loadtest-5.1.0.tgz#20dd700329d98612b23c7e6fa0d1d4661ea9bccf" + integrity sha512-LFMyFMA77o41JyNPn+FOXNN/SNURXys8KQNK83mR6bDHjH/XpA5Uz8dd4lofh2VITrDsK+ITKi8QNkmxTOFt1Q== + dependencies: + agentkeepalive "^2.0.3" + confinode "^2.1.1" + https-proxy-agent "^2.2.1" + log "1.4.*" + stdio "^0.2.3" + testing "^1.1.1" + websocket "^1.0.28" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5400,6 +5460,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +log@1.4.*, log@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/log/-/log-1.4.0.tgz#4ba1d890fde249b031dca03bc37eaaf325656f1c" + integrity sha1-S6HYkP3iSbAx3KA7w36q8yVlbxw= + loglevel-colored-level-prefix@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" @@ -5982,6 +6047,11 @@ node-fetch@2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-gyp-build@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" + integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== + node-html-parser@^1.2.19: version "1.2.20" resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.2.20.tgz#37e9ebc627dbe3ff446eea4ac93e3d254b7c6ee4" @@ -7147,6 +7217,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -8113,6 +8188,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +stdio@^0.2.3: + version "0.2.7" + resolved "https://registry.yarnpkg.com/stdio/-/stdio-0.2.7.tgz#a1c57da10fe1cfaa0c3bf683c9d0743d1b660839" + integrity sha1-ocV9oQ/hz6oMO/aDydB0PRtmCDk= + stream-browserify@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -8584,6 +8664,13 @@ terser@^5.0.0: source-map "~0.6.1" source-map-support "~0.5.12" +testing@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/testing/-/testing-1.1.2.tgz#575b123070f63c5068e943cf255dbae71c5d8ba6" + integrity sha512-+wHrDL29KsI3NQtgGmgdZ/MaUZhnVePbt5ZfiMn6ntDpv/kMWfdiBrg/lJqntor9H8+zQYxvfPLVowPPs1nVEg== + dependencies: + log "1.4.0" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -8992,6 +9079,13 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf-8-validate@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3" + integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw== + dependencies: + node-gyp-build "~3.7.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -9168,6 +9262,18 @@ webpack@4.44.1: watchpack "^1.7.4" webpack-sources "^1.4.1" +websocket@^1.0.28: + version "1.0.32" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" + integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -9268,6 +9374,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" From 561491c2aad7e11fdbcdf0ddf1de0c0a800ea921 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 12:15:42 -0700 Subject: [PATCH 12/71] Updated language menu. --- components/common/Menu.module.css | 1 + components/common/MenuButton.js | 3 ++- components/settings/LanguageButton.js | 2 ++ components/settings/LanguageButton.module.css | 12 ++++++++++++ lib/lang.js | 2 +- package.json | 2 +- 6 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 components/settings/LanguageButton.module.css diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index 369e37c8..7ebf8e9d 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -1,4 +1,5 @@ .menu { + background: var(--gray50); border: 1px solid var(--gray500); border-radius: 4px; overflow: hidden; diff --git a/components/common/MenuButton.js b/components/common/MenuButton.js index 62306570..7035b1ef 100644 --- a/components/common/MenuButton.js +++ b/components/common/MenuButton.js @@ -9,6 +9,7 @@ export default function MenuButton({ icon, value, options, + menuClassname, menuPosition = 'bottom', menuAlign = 'right', onSelect, @@ -45,7 +46,7 @@ export default function MenuButton({ {showMenu && ( } options={menuOptions} value={locale} + menuClassname={styles.menu} renderValue={option => option?.display} onSelect={handleSelect} /> diff --git a/components/settings/LanguageButton.module.css b/components/settings/LanguageButton.module.css new file mode 100644 index 00000000..da909df5 --- /dev/null +++ b/components/settings/LanguageButton.module.css @@ -0,0 +1,12 @@ +.menu { + display: flex; + flex-flow: row wrap; + min-width: 500px; + max-width: 100vw; + padding: 10px; +} + +.menu div { + border-radius: 5px; + min-width: 33%; +} diff --git a/lib/lang.js b/lib/lang.js index a6d311b5..388c58e5 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -59,10 +59,10 @@ export const dateLocales = { }; export const menuOptions = [ - { label: 'English', value: 'en-US', display: 'en' }, { label: '中文', value: 'zh-CN', display: 'cn' }, { label: 'Dansk', value: 'da-DK', display: 'da' }, { label: 'Deutsch', value: 'de-DE', display: 'de' }, + { label: 'English', value: 'en-US', display: 'en' }, { label: 'Español', value: 'es-MX', display: 'es' }, { label: 'Føroyskt', value: 'fo-FO', display: 'fo' }, { label: 'Français', value: 'fr-FR', display: 'fr' }, diff --git a/package.json b/package.json index 656e535c..6aef7812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.70.0", + "version": "0.71.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From 0b131392fc9dd32bcd881603fddf284f44310fc8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 12:16:07 -0700 Subject: [PATCH 13/71] Updated language menu. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6aef7812..df40306e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.71.0", + "version": "0.72.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From 4009a002967e44ad1a4e01393a0123ce6d0f4284 Mon Sep 17 00:00:00 2001 From: Herdi Tr <35950229+Hrdtr@users.noreply.github.com> Date: Sat, 3 Oct 2020 04:00:16 +0700 Subject: [PATCH 14/71] Indonesian Language Translation --- lang/id-ID.json | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lang/id-ID.json diff --git a/lang/id-ID.json b/lang/id-ID.json new file mode 100644 index 00000000..a414812f --- /dev/null +++ b/lang/id-ID.json @@ -0,0 +1,97 @@ +{ + "button.add-account": "Tambah akun", + "button.add-website": "Tambah situs web", + "button.back": "Kembali", + "button.cancel": "Batal", + "button.change-password": "Ganti password", + "button.copy-to-clipboard": "Salin ke papan klip", + "button.date-range": "Rentang tanggal", + "button.delete": "Hapus", + "button.dismiss": "Tutup", + "button.edit": "Sunting", + "button.login": "Masuk", + "button.more": "Lebih banyak", + "button.refresh": "Segarkan", + "button.reset": "Atur ulang", + "button.save": "Simpan", + "button.single-day": "Sehari", + "button.view-details": "Lihat Detil", + "label.accounts": "Akun", + "label.administrator": "Pengelola", + "label.confirm-password": "Konfirmasi kata sandi", + "label.current-password": "Kata sandi sekarang", + "label.custom-range": "Rentang khusus", + "label.dashboard": "Dasbor", + "label.default-date-range": "Rentang tanggal default", + "label.domain": "Domain", + "label.enable-share-url": "Aktifkan URL berbagi", + "label.invalid": "Tidak valid", + "label.invalid-domain": "Domain tidak valid", + "label.last-days": "{x} hari terakhir", + "label.last-hours": "{x} jam terakhir", + "label.logged-in-as": "Masuk sebagai {username}", + "label.logout": "Keluar", + "label.name": "Nama", + "label.new-password": "Kata sandi baru", + "label.password": "Kata sandi", + "label.passwords-dont-match": "Kata sandi tidak cocok", + "label.profile": "Profil", + "label.required": "Wajib", + "label.settings": "Pengaturan", + "label.this-month": "Bulan ini", + "label.this-week": "Minggu ini", + "label.this-year": "Tahun ini", + "label.timezone": "Zona waktu", + "label.today": "Hari ini", + "label.unknown": "Tidak diketahui", + "label.username": "Nama pengguna", + "label.websites": "Situs web", + "message.active-users": "{x} sekarang {x, plural, one {visitor} other {visitors}}", + "message.confirm-delete": "Apakah kamu yakin ingin menghapus {target}?", + "message.copied": "Tersalin!", + "message.delete-warning": "Semua data terkait juga akan dihapus.", + "message.failure": "Ada yang salah.", + "message.get-share-url": "Dapatkan URL berbagi", + "message.get-tracking-code": "Dapatkan kode pelacakan", + "message.go-to-settings": "Pergi ke pengaturan", + "message.incorrect-username-password": "Nama pengguna/kata sandi salah.", + "message.new-version-available": "Versi terbaru umami {version} telah tersedia!", + "message.no-data-available": "Tidak ada data.", + "message.no-websites-configured": "Anda tidak memiliki situs web yang dikonfigurasi.", + "message.page-not-found": "Halaman tidak ditemukan.", + "message.powered-by": "Didukung oleh {name}", + "message.save-success": "Berhasil disimpan.", + "message.share-url": "Ini adalah URL yang dibagikan secara publik untuk {target}.", + "message.track-stats": "Untuk melacak statistik {target}, tempatkan kode berikut di bagian {head} situs web anda.", + "message.type-delete": "Ketikkan {delete} pada kotak di bawah untuk konfirmasi.", + "metrics.actions": "Aksi", + "metrics.average-visit-time": "Waktu kunjungan rata-rata", + "metrics.bounce-rate": "Tingkat bouncing", + "metrics.browsers": "Browser", + "metrics.countries": "Negara", + "metrics.device.desktop": "Desktop", + "metrics.device.laptop": "Laptop", + "metrics.device.mobile": "Ponsel", + "metrics.device.tablet": "Tablet", + "metrics.devices": "Perangkat", + "metrics.events": "Perihal", + "metrics.filter.combined": "Gabungan", + "metrics.filter.domain-only": "Hanya domain", + "metrics.filter.raw": "Raw", + "metrics.operating-systems": "Sistem Operasi", + "metrics.page-views": "Tampilan halaman", + "metrics.pages": "Halaman", + "metrics.referrers": "Perujuk", + "metrics.unique-visitors": "Pengunjung unik", + "metrics.views": "Tampilan", + "metrics.visitors": "Pengunjung", + "title.add-account": "Tambah akun", + "title.add-website": "Tambah situs web", + "title.change-password": "Ganti kata sandi", + "title.delete-account": "Hapus akun", + "title.delete-website": "Hapus situs web", + "title.edit-account": "Sunting akun", + "title.edit-website": "Sunting situs web", + "title.share-url": "Bagikan URL", + "title.tracking-code": "Kode lacak" +} From 3d1dc084912849402ef9b07f0f27594fbc702a5e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 2 Oct 2020 20:33:46 -0700 Subject: [PATCH 15/71] Implement session caching. --- lib/session.js | 12 ++++++++++-- package.json | 2 +- pages/api/collect.js | 7 +++++-- tracker/index.js | 21 +++++++++++++++------ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/lib/session.js b/lib/session.js index 0488b730..3d2fc7bc 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,6 +1,6 @@ import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries'; import { getClientInfo } from 'lib/request'; -import { uuid, isValidUuid } from 'lib/crypto'; +import { uuid, isValidUuid, parseToken } from 'lib/crypto'; export async function getSession(req) { const { payload } = req.body; @@ -9,7 +9,15 @@ export async function getSession(req) { throw new Error('Invalid request'); } - const { website: website_uuid, hostname, screen, language } = payload; + const { website: website_uuid, hostname, screen, language, cache } = payload; + + if (cache) { + const result = await parseToken(cache); + + if (result) { + return result; + } + } if (!isValidUuid(website_uuid)) { throw new Error(`Invalid website: ${website_uuid}`); diff --git a/package.json b/package.json index df40306e..54512997 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.72.0", + "version": "0.73.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/pages/api/collect.js b/pages/api/collect.js index 399e88e9..e6b3e0ca 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -1,7 +1,8 @@ +import isBot from 'isbot-fast'; import { savePageView, saveEvent } from 'lib/queries'; import { useCors, useSession } from 'lib/middleware'; import { ok, badRequest } from 'lib/response'; -import isBot from 'isbot-fast'; +import { createToken } from 'lib/crypto'; export default async (req, res) => { if (isBot(req.headers['user-agent'])) { @@ -28,5 +29,7 @@ export default async (req, res) => { return badRequest(res); } - return ok(res); + const token = await createToken({ website_id, session_id }); + + return ok(res, token); }; diff --git a/tracker/index.js b/tracker/index.js index 12119298..154e0034 100644 --- a/tracker/index.js +++ b/tracker/index.js @@ -6,6 +6,7 @@ import { removeTrailingSlash } from '../lib/url'; screen: { width, height }, navigator: { language }, location: { hostname, pathname, search }, + sessionStorage, document, history, } = window; @@ -16,7 +17,8 @@ import { removeTrailingSlash } from '../lib/url'; const website = attr('data-website-id'); const hostUrl = attr('data-host-url'); const autoTrack = attr('data-auto-track') !== 'false'; - const dnt = attr('data-do-not-track') === 'true'; + const dnt = attr('data-do-not-track'); + const useCache = attr('data-cache'); if (!script || (dnt && doNotTrack())) return; @@ -37,7 +39,7 @@ import { removeTrailingSlash } from '../lib/url'; req.onreadystatechange = () => { if (req.readyState === 4) { - callback && callback(); + callback && callback(req.response); } }; @@ -45,11 +47,14 @@ import { removeTrailingSlash } from '../lib/url'; }; const collect = (type, params, uuid) => { + const key = 'umami.cache'; + const payload = { website: uuid, hostname, screen, language, + cache: useCache && sessionStorage.getItem(key), }; if (params) { @@ -58,10 +63,14 @@ import { removeTrailingSlash } from '../lib/url'; }); } - return post(`${root}/api/collect`, { - type, - payload, - }); + return post( + `${root}/api/collect`, + { + type, + payload, + }, + res => useCache && sessionStorage.setItem(key, res), + ); }; const trackView = (url = currentUrl, referrer = currentRef, uuid = website) => From 2b1a0bad0c36bd6a42df3acc469aa89ba437ad43 Mon Sep 17 00:00:00 2001 From: Herdi Tr <35950229+Hrdtr@users.noreply.github.com> Date: Sat, 3 Oct 2020 16:20:08 +0700 Subject: [PATCH 16/71] Update Indonesian Language Translation --- lang/id-ID.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/id-ID.json b/lang/id-ID.json index a414812f..8099cd75 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -46,7 +46,7 @@ "label.unknown": "Tidak diketahui", "label.username": "Nama pengguna", "label.websites": "Situs web", - "message.active-users": "{x} sekarang {x, plural, one {visitor} other {visitors}}", + "message.active-users": "{x} pengunjung saat ini", "message.confirm-delete": "Apakah kamu yakin ingin menghapus {target}?", "message.copied": "Tersalin!", "message.delete-warning": "Semua data terkait juga akan dihapus.", From 7741fe8ca657362b007262baf64c4a97775d0cc7 Mon Sep 17 00:00:00 2001 From: Denys M Date: Sat, 3 Oct 2020 17:21:06 +0400 Subject: [PATCH 17/71] Add Ukrainian translation (`uk_UA`) --- lang/uk_UA.json | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lang/uk_UA.json diff --git a/lang/uk_UA.json b/lang/uk_UA.json new file mode 100644 index 00000000..9c5065bf --- /dev/null +++ b/lang/uk_UA.json @@ -0,0 +1,97 @@ +{ + "button.add-account": "Додати обліковий запис", + "button.add-website": "Додати веб-сайт", + "button.back": "Назад", + "button.cancel": "Відмінити", + "button.change-password": "Змінити пароль", + "button.copy-to-clipboard": "Копіювати до буферу обміну", + "button.date-range": "Діапазон дат", + "button.delete": "Видалити", + "button.dismiss": "Відхилити", + "button.edit": "Редагувати", + "button.login": "Увійти", + "button.more": "Більше", + "button.refresh": "Оновити", + "button.reset": "Скинути", + "button.save": "Зберегти", + "button.single-day": "Один день", + "button.view-details": "Переглянути деталі", + "label.accounts": "Облікові записи", + "label.administrator": "Адміністратор", + "label.confirm-password": "Підтвердити пароль", + "label.current-password": "Поточний пароль", + "label.custom-range": "Довільний період", + "label.dashboard": "Інформаційна панель", + "label.default-date-range": "Діапазон дат за умовчанням", + "label.domain": "Домен", + "label.enable-share-url": "Дозволити ділитися посиланням", + "label.invalid": "Некоректний", + "label.invalid-domain": "Некоректний домен", + "label.last-days": "Останні {x} днів", + "label.last-hours": "Останні {x} годин", + "label.logged-in-as": "Ви увійшли як {username}", + "label.logout": "Вийти", + "label.name": "Ім'я", + "label.new-password": "Новий пароль", + "label.password": "Пароль", + "label.passwords-dont-match": "Паролі не співпадають", + "label.profile": "Профіль", + "label.required": "Обов'язкове", + "label.settings": "Налаштування", + "label.this-month": "Поточний місяць", + "label.this-week": "Поточний тиждень", + "label.this-year": "Поточний рік", + "label.timezone": "Часовий пояс", + "label.today": "Сьогодні", + "label.unknown": "Невідомо", + "label.username": "Ім'я користувача", + "label.websites": "Веб-сайти", + "message.active-users": "{x} поточних відвідувачів", + "message.confirm-delete": "Ви впевнені, що бажаєте видалити {target}?", + "message.copied": "Скопійовано!", + "message.delete-warning": "Усі пов'язані дані будуть видалені також.", + "message.failure": "Щось пішло не так.", + "message.get-share-url": "Отримати публічне посилання", + "message.get-tracking-code": "Отримати код для відслідковування", + "message.go-to-settings": "Перейти до налаштувань", + "message.incorrect-username-password": "Невірне ім'я користувача або пароль.", + "message.new-version-available": "Нова версія umami {version} доступна!", + "message.no-data-available": "Немає даних.", + "message.no-websites-configured": "У вас немає налаштованих веб-сайтів.", + "message.page-not-found": "Сторінку не знайдено.", + "message.powered-by": "На базі {name}", + "message.save-success": "Збережено успішно.", + "message.share-url": "Це публічне посилання для {target}.", + "message.track-stats": "Або відслідковувати статистику для {target}, розмістіть наступний код у {head} секції вашого веб-сайту.", + "message.type-delete": "Введіть {delete} у полі нижче щоб підтвердити.", + "metrics.actions": "Дії", + "metrics.average-visit-time": "Середній час візиту", + "metrics.bounce-rate": "Показник відмов", + "metrics.browsers": "Браузери", + "metrics.countries": "Країни", + "metrics.device.desktop": "Настільний комп'ютер", + "metrics.device.laptop": "Ноутбук", + "metrics.device.mobile": "Мобільний", + "metrics.device.tablet": "Планшет", + "metrics.devices": "Пристрої", + "metrics.events": "Події", + "metrics.filter.combined": "Об'єднані", + "metrics.filter.domain-only": "Лише домен", + "metrics.filter.raw": "Сирі дані", + "metrics.operating-systems": "Операційна система", + "metrics.page-views": "Перегляди сторінок", + "metrics.pages": "Сторінки", + "metrics.referrers": "Джерела", + "metrics.unique-visitors": "Унікальні відвідувачі", + "metrics.views": "Перегляди", + "metrics.visitors": "Відвідувачі", + "title.add-account": "Додати обліковий запис", + "title.add-website": "Додати website", + "title.change-password": "Змінити пароль", + "title.delete-account": "Видалити обліковий запис", + "title.delete-website": "Видалити веб-сайт", + "title.edit-account": "Редагувати обліковий запис", + "title.edit-website": "Редагувати веб-сайт", + "title.share-url": "Поділитися посилання", + "title.tracking-code": "Код для відслідковування" +} From 7d77afb88db249f00de2291cfa781f8eb689b400 Mon Sep 17 00:00:00 2001 From: Denys M Date: Sat, 3 Oct 2020 21:03:48 +0400 Subject: [PATCH 18/71] Fix l10n file name for Ukrainian language --- lang/{uk_UA.json => uk-UA.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lang/{uk_UA.json => uk-UA.json} (100%) diff --git a/lang/uk_UA.json b/lang/uk-UA.json similarity index 100% rename from lang/uk_UA.json rename to lang/uk-UA.json From bba75fd67f9ed8a06448f0e4092149f0329741fa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 10:53:06 -0700 Subject: [PATCH 19/71] CSS fixes for mobile. --- components/common/Loading.module.css | 2 ++ components/common/Menu.module.css | 2 +- components/common/MenuButton.js | 2 +- components/common/MenuButton.module.css | 4 ---- components/layout/Footer.js | 9 +++++---- components/layout/Footer.module.css | 11 +++++++++++ components/metrics/MetricsBar.module.css | 1 + components/settings/LanguageButton.module.css | 15 ++++++++++++++- 8 files changed, 35 insertions(+), 11 deletions(-) diff --git a/components/common/Loading.module.css b/components/common/Loading.module.css index 2a210078..4e56dd8e 100644 --- a/components/common/Loading.module.css +++ b/components/common/Loading.module.css @@ -12,6 +12,8 @@ .loading { display: flex; + justify-content: center; + align-items: center; position: absolute; top: 50%; left: 50%; diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index 7ebf8e9d..d2ad2cc4 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -3,7 +3,7 @@ border: 1px solid var(--gray500); border-radius: 4px; overflow: hidden; - z-index: 2; + z-index: 100; } .option { diff --git a/components/common/MenuButton.js b/components/common/MenuButton.js index 7035b1ef..4f3f2584 100644 --- a/components/common/MenuButton.js +++ b/components/common/MenuButton.js @@ -46,7 +46,7 @@ export default function MenuButton({ {showMenu && ( -
-
-
+
+
+
-
{`v${current}`}
+
{`v${current}`}
); diff --git a/components/layout/Footer.module.css b/components/layout/Footer.module.css index 7c671d7e..a83c8c3c 100644 --- a/components/layout/Footer.module.css +++ b/components/layout/Footer.module.css @@ -4,4 +4,15 @@ align-items: center; font-size: var(--font-size-small); min-height: 100px; + text-align: center; +} + +.version { + text-align: right; +} + +@media only screen and (max-width: 768px) { + .version { + text-align: center; + } } diff --git a/components/metrics/MetricsBar.module.css b/components/metrics/MetricsBar.module.css index 4546f893..b13e974f 100644 --- a/components/metrics/MetricsBar.module.css +++ b/components/metrics/MetricsBar.module.css @@ -1,6 +1,7 @@ .bar { display: flex; cursor: pointer; + min-height: 80px; } .bar > div + div { diff --git a/components/settings/LanguageButton.module.css b/components/settings/LanguageButton.module.css index da909df5..e1f840d5 100644 --- a/components/settings/LanguageButton.module.css +++ b/components/settings/LanguageButton.module.css @@ -8,5 +8,18 @@ .menu div { border-radius: 5px; - min-width: 33%; + min-width: calc(100% / 3); +} + +@media only screen and (max-width: 992px) { + .menu { + min-width: 90vw; + transform: translateX(calc(40vw)); + } +} + +@media only screen and (max-width: 768px) { + .menu div { + min-width: 50%; + } } From f6f2c4b5e19eb55e6f895f97b3f89d083d809074 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 11:21:44 -0700 Subject: [PATCH 20/71] Add country name download script. --- lang/{uk_UA.json => uk-UA.json} | 0 package.json | 3 ++- public/country/id-ID.json | 1 + public/country/uk-UA.json | 1 + scripts/download-country-names.js | 39 +++++++++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) rename lang/{uk_UA.json => uk-UA.json} (100%) create mode 100644 public/country/id-ID.json create mode 100644 public/country/uk-UA.json create mode 100644 scripts/download-country-names.js diff --git a/lang/uk_UA.json b/lang/uk-UA.json similarity index 100% rename from lang/uk_UA.json rename to lang/uk-UA.json diff --git a/package.json b/package.json index 54512997..9c6e899e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.73.0", + "version": "0.74.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -32,6 +32,7 @@ "format-lang": "node scripts/format-lang.js", "compile-lang": "formatjs compile-folder --ast build lang-compiled", "check-lang": "node scripts/check-lang.js", + "download-country-names": "node scripts/download-country-names.js", "loadtest": "node scripts/loadtest.js", "loadtest:medium": "node scripts/loadtest.js --weight=medium", "loadtest:heavy": "node scripts/loadtest.js --weight=heavy --verbose" diff --git a/public/country/id-ID.json b/public/country/id-ID.json new file mode 100644 index 00000000..13a6220a --- /dev/null +++ b/public/country/id-ID.json @@ -0,0 +1 @@ +{"AF":"Afganistan","ZA":"Afrika Selatan","AL":"Albania","DZ":"Aljazair","US":"Amerika Serikat","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktika","AG":"Antigua dan Barbuda","SA":"Arab Saudi","AR":"Argentina","AM":"Armenia","AW":"Aruba","AU":"Australia","AT":"Austria","AZ":"Azerbaijan","BS":"Bahama","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","NL":"Belanda","BQ":"Belanda Karibia","BY":"Belarus","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia dan Herzegovina","BW":"Botswana","BR":"Brasil","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","TD":"Cad","CZ":"Ceko","CL":"Cile","CI":"C\u00f4te d\u2019Ivoire","CW":"Cura\u00e7ao","DK":"Denmark","DM":"Dominika","EC":"Ekuador","SV":"El Salvador","ER":"Eritrea","EE":"Estonia","SZ":"eSwatini","ET":"Etiopia","FJ":"Fiji","PH":"Filipina","FI":"Finlandia","GA":"Gabon","GM":"Gambia","GE":"Georgia","GS":"Georgia Selatan & Kep. Sandwich Selatan","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Grinlandia","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GQ":"Guinea Ekuatorial","GW":"Guinea-Bissau","GY":"Guyana","GF":"Guyana Prancis","HT":"Haiti","HN":"Honduras","HK":"Hong Kong DAK Tiongkok","HU":"Hungaria","IN":"India","ID":"Indonesia","GB":"Inggris Raya","IQ":"Irak","IR":"Iran","IE":"Irlandia","IS":"Islandia","IL":"Israel","IT":"Italia","JM":"Jamaika","JP":"Jepang","DE":"Jerman","JE":"Jersey","DJ":"Jibuti","NC":"Kaledonia Baru","KH":"Kamboja","CM":"Kamerun","CA":"Kanada","KZ":"Kazakstan","KE":"Kenya","AX":"Kepulauan Aland","KY":"Kepulauan Cayman","CC":"Kepulauan Cocos (Keeling)","CK":"Kepulauan Cook","FK":"Kepulauan Falkland","FO":"Kepulauan Faroe","MP":"Kepulauan Mariana Utara","MH":"Kepulauan Marshall","NF":"Kepulauan Norfolk","PN":"Kepulauan Pitcairn","SB":"Kepulauan Solomon","SJ":"Kepulauan Svalbard dan Jan Mayen","UM":"Kepulauan Terluar A.S.","TC":"Kepulauan Turks dan Caicos","VI":"Kepulauan Virgin Amerika Serikat","VG":"Kepulauan Virgin Britania Raya","WF":"Kepulauan Wallis dan Futuna","KG":"Kirgistan","KI":"Kiribati","CO":"Kolombia","KM":"Komoro","CG":"Kongo - Brazzaville","CD":"Kongo - Kinshasa","KR":"Korea Selatan","KP":"Korea Utara","CR":"Kosta Rika","HR":"Kroasia","CU":"Kuba","KW":"Kuwait","LA":"Laos","LV":"Latvia","LB":"Lebanon","LS":"Lesotho","LR":"Liberia","LY":"Libia","LI":"Liechtenstein","LT":"Lituania","LU":"Luksemburg","MG":"Madagaskar","MO":"Makau DAK Tiongkok","MK":"Makedonia Utara","MV":"Maladewa","MW":"Malawi","MY":"Malaysia","ML":"Mali","MT":"Malta","MA":"Maroko","MQ":"Martinik","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Meksiko","EG":"Mesir","FM":"Mikronesia","MD":"Moldova","MC":"Monako","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mozambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NE":"Niger","NG":"Nigeria","NI":"Nikaragua","NU":"Niue","NO":"Norwegia","OM":"Oman","PK":"Pakistan","PW":"Palau","PA":"Panama","PG":"Papua Nugini","PY":"Paraguay","PE":"Peru","PL":"Polandia","PF":"Polinesia Prancis","PT":"Portugal","FR":"Prancis","PR":"Puerto Riko","BV":"Pulau Bouvet","HM":"Pulau Heard dan Kepulauan McDonald","IM":"Pulau Man","CX":"Pulau Natal","QA":"Qatar","CF":"Republik Afrika Tengah","DO":"Republik Dominika","RE":"R\u00e9union","RO":"Rumania","RU":"Rusia","RW":"Rwanda","EH":"Sahara Barat","BL":"Saint Barth\u00e9lemy","SH":"Saint Helena","KN":"Saint Kitts dan Nevis","LC":"Saint Lucia","MF":"Saint Martin","PM":"Saint Pierre dan Miquelon","VC":"Saint Vincent dan Grenadine","WS":"Samoa","AS":"Samoa Amerika","SM":"San Marino","ST":"Sao Tome dan Principe","NZ":"Selandia Baru","SN":"Senegal","RS":"Serbia","SC":"Seychelles","SL":"Sierra Leone","SG":"Singapura","SX":"Sint Maarten","CY":"Siprus","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","ES":"Spanyol","LK":"Sri Lanka","SD":"Sudan","SS":"Sudan Selatan","SY":"Suriah","SR":"Suriname","SE":"Swedia","CH":"Swiss","TW":"Taiwan","TJ":"Tajikistan","CV":"Tanjung Verde","TZ":"Tanzania","TH":"Thailand","TL":"Timor Leste","CN":"Tiongkok","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad dan Tobago","TN":"Tunisia","TR":"Turki","TM":"Turkimenistan","TV":"Tuvalu","UG":"Uganda","UA":"Ukraina","AE":"Uni Emirat Arab","UY":"Uruguay","UZ":"Uzbekistan","VU":"Vanuatu","VA":"Vatikan","VE":"Venezuela","VN":"Vietnam","IO":"Wilayah Inggris di Samudra Hindia","PS":"Wilayah Palestina","TF":"Wilayah Selatan Perancis","YE":"Yaman","JO":"Yordania","GR":"Yunani","ZM":"Zambia","ZW":"Zimbabwe"} \ No newline at end of file diff --git a/public/country/uk-UA.json b/public/country/uk-UA.json new file mode 100644 index 00000000..b2605d87 --- /dev/null +++ b/public/country/uk-UA.json @@ -0,0 +1 @@ +{"AU":"\u0410\u0432\u0441\u0442\u0440\u0430\u043b\u0456\u044f","AT":"\u0410\u0432\u0441\u0442\u0440\u0456\u044f","AZ":"\u0410\u0437\u0435\u0440\u0431\u0430\u0439\u0434\u0436\u0430\u043d","AX":"\u0410\u043b\u0430\u043d\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","AL":"\u0410\u043b\u0431\u0430\u043d\u0456\u044f","DZ":"\u0410\u043b\u0436\u0438\u0440","AS":"\u0410\u043c\u0435\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0435 \u0421\u0430\u043c\u043e\u0430","AO":"\u0410\u043d\u0433\u043e\u043b\u0430","AI":"\u0410\u043d\u0491\u0456\u043b\u044c\u044f","AD":"\u0410\u043d\u0434\u043e\u0440\u0440\u0430","AQ":"\u0410\u043d\u0442\u0430\u0440\u043a\u0442\u0438\u043a\u0430","AG":"\u0410\u043d\u0442\u0438\u0491\u0443\u0430 \u0456 \u0411\u0430\u0440\u0431\u0443\u0434\u0430","AR":"\u0410\u0440\u0433\u0435\u043d\u0442\u0438\u043d\u0430","AW":"\u0410\u0440\u0443\u0431\u0430","AF":"\u0410\u0444\u0433\u0430\u043d\u0456\u0441\u0442\u0430\u043d","BS":"\u0411\u0430\u0433\u0430\u043c\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BD":"\u0411\u0430\u043d\u0433\u043b\u0430\u0434\u0435\u0448","BB":"\u0411\u0430\u0440\u0431\u0430\u0434\u043e\u0441","BH":"\u0411\u0430\u0445\u0440\u0435\u0439\u043d","BZ":"\u0411\u0435\u043b\u0456\u0437","BE":"\u0411\u0435\u043b\u044c\u0491\u0456\u044f","BJ":"\u0411\u0435\u043d\u0456\u043d","BM":"\u0411\u0435\u0440\u043c\u0443\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BY":"\u0411\u0456\u043b\u043e\u0440\u0443\u0441\u044c","BG":"\u0411\u043e\u043b\u0433\u0430\u0440\u0456\u044f","BO":"\u0411\u043e\u043b\u0456\u0432\u0456\u044f","BA":"\u0411\u043e\u0441\u043d\u0456\u044f \u0456 \u0413\u0435\u0440\u0446\u0435\u0491\u043e\u0432\u0438\u043d\u0430","BW":"\u0411\u043e\u0442\u0441\u0432\u0430\u043d\u0430","BR":"\u0411\u0440\u0430\u0437\u0456\u043b\u0456\u044f","IO":"\u0411\u0440\u0438\u0442\u0430\u043d\u0441\u044c\u043a\u0430 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u044f \u0432 \u0406\u043d\u0434\u0456\u0439\u0441\u044c\u043a\u043e\u043c\u0443 \u041e\u043a\u0435\u0430\u043d\u0456","VG":"\u0411\u0440\u0438\u0442\u0430\u043d\u0441\u044c\u043a\u0456 \u0412\u0456\u0440\u0433\u0456\u043d\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438","BN":"\u0411\u0440\u0443\u043d\u0435\u0439","BF":"\u0411\u0443\u0440\u043a\u0456\u043d\u0430-\u0424\u0430\u0441\u043e","BI":"\u0411\u0443\u0440\u0443\u043d\u0434\u0456","BT":"\u0411\u0443\u0442\u0430\u043d","VU":"\u0412\u0430\u043d\u0443\u0430\u0442\u0443","VA":"\u0412\u0430\u0442\u0438\u043a\u0430\u043d","GB":"\u0412\u0435\u043b\u0438\u043a\u0430 \u0411\u0440\u0438\u0442\u0430\u043d\u0456\u044f","VE":"\u0412\u0435\u043d\u0435\u0441\u0443\u0435\u043b\u0430","UM":"\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438 \u0421\u0428\u0410","VI":"\u0412\u0456\u0440\u0433\u0456\u043d\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438, \u0421\u0428\u0410","AM":"\u0412\u0456\u0440\u043c\u0435\u043d\u0456\u044f","VN":"\u0412\u02bc\u0454\u0442\u043d\u0430\u043c","GA":"\u0413\u0430\u0431\u043e\u043d","HT":"\u0413\u0430\u0457\u0442\u0456","GM":"\u0413\u0430\u043c\u0431\u0456\u044f","GH":"\u0413\u0430\u043d\u0430","GN":"\u0413\u0432\u0456\u043d\u0435\u044f","GW":"\u0413\u0432\u0456\u043d\u0435\u044f-\u0411\u0456\u0441\u0430\u0443","HN":"\u0413\u043e\u043d\u0434\u0443\u0440\u0430\u0441","HK":"\u0413\u043e\u043d\u043a\u043e\u043d\u0433, \u041e.\u0410.\u0420. \u041a\u0438\u0442\u0430\u044e","GR":"\u0413\u0440\u0435\u0446\u0456\u044f","GE":"\u0413\u0440\u0443\u0437\u0456\u044f","GY":"\u0490\u0430\u0439\u0430\u043d\u0430","GP":"\u0490\u0432\u0430\u0434\u0435\u043b\u0443\u043f\u0430","GT":"\u0490\u0432\u0430\u0442\u0435\u043c\u0430\u043b\u0430","GG":"\u0490\u0435\u0440\u043d\u0441\u0456","GI":"\u0490\u0456\u0431\u0440\u0430\u043b\u0442\u0430\u0440","GD":"\u0490\u0440\u0435\u043d\u0430\u0434\u0430","GL":"\u0490\u0440\u0435\u043d\u043b\u0430\u043d\u0434\u0456\u044f","GU":"\u0490\u0443\u0430\u043c","DK":"\u0414\u0430\u043d\u0456\u044f","JE":"\u0414\u0436\u0435\u0440\u0441\u0456","DJ":"\u0414\u0436\u0438\u0431\u0443\u0442\u0456","DM":"\u0414\u043e\u043c\u0456\u043d\u0456\u043a\u0430","DO":"\u0414\u043e\u043c\u0456\u043d\u0456\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","EC":"\u0415\u043a\u0432\u0430\u0434\u043e\u0440","GQ":"\u0415\u043a\u0432\u0430\u0442\u043e\u0440\u0456\u0430\u043b\u044c\u043d\u0430 \u0413\u0432\u0456\u043d\u0435\u044f","ER":"\u0415\u0440\u0438\u0442\u0440\u0435\u044f","SZ":"\u0415\u0441\u0432\u0430\u0442\u0456\u043d\u0456","EE":"\u0415\u0441\u0442\u043e\u043d\u0456\u044f","ET":"\u0415\u0444\u0456\u043e\u043f\u0456\u044f","EG":"\u0404\u0433\u0438\u043f\u0435\u0442","YE":"\u0404\u043c\u0435\u043d","ZM":"\u0417\u0430\u043c\u0431\u0456\u044f","EH":"\u0417\u0430\u0445\u0456\u0434\u043d\u0430 \u0421\u0430\u0445\u0430\u0440\u0430","ZW":"\u0417\u0456\u043c\u0431\u0430\u0431\u0432\u0435","IL":"\u0406\u0437\u0440\u0430\u0457\u043b\u044c","IN":"\u0406\u043d\u0434\u0456\u044f","ID":"\u0406\u043d\u0434\u043e\u043d\u0435\u0437\u0456\u044f","IQ":"\u0406\u0440\u0430\u043a","IR":"\u0406\u0440\u0430\u043d","IE":"\u0406\u0440\u043b\u0430\u043d\u0434\u0456\u044f","IS":"\u0406\u0441\u043b\u0430\u043d\u0434\u0456\u044f","ES":"\u0406\u0441\u043f\u0430\u043d\u0456\u044f","IT":"\u0406\u0442\u0430\u043b\u0456\u044f","JO":"\u0419\u043e\u0440\u0434\u0430\u043d\u0456\u044f","CV":"\u041a\u0430\u0431\u043e-\u0412\u0435\u0440\u0434\u0435","KZ":"\u041a\u0430\u0437\u0430\u0445\u0441\u0442\u0430\u043d","KY":"\u041a\u0430\u0439\u043c\u0430\u043d\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","KH":"\u041a\u0430\u043c\u0431\u043e\u0434\u0436\u0430","CM":"\u041a\u0430\u043c\u0435\u0440\u0443\u043d","CA":"\u041a\u0430\u043d\u0430\u0434\u0430","QA":"\u041a\u0430\u0442\u0430\u0440","KE":"\u041a\u0435\u043d\u0456\u044f","KG":"\u041a\u0438\u0440\u0433\u0438\u0437\u0441\u0442\u0430\u043d","CN":"\u041a\u0438\u0442\u0430\u0439","CY":"\u041a\u0456\u043f\u0440","KI":"\u041a\u0456\u0440\u0456\u0431\u0430\u0442\u0456","CC":"\u041a\u043e\u043a\u043e\u0441\u043e\u0432\u0456 (\u041a\u0456\u043b\u0456\u043d\u0491) \u041e\u0441\u0442\u0440\u043e\u0432\u0438","CO":"\u041a\u043e\u043b\u0443\u043c\u0431\u0456\u044f","KM":"\u041a\u043e\u043c\u043e\u0440\u0438","CG":"\u041a\u043e\u043d\u0433\u043e \u2013 \u0411\u0440\u0430\u0437\u0437\u0430\u0432\u0456\u043b\u044c","CD":"\u041a\u043e\u043d\u0433\u043e \u2013 \u041a\u0456\u043d\u0448\u0430\u0441\u0430","CR":"\u041a\u043e\u0441\u0442\u0430-\u0420\u0456\u043a\u0430","CI":"\u041a\u043e\u0442-\u0434\u02bc\u0406\u0432\u0443\u0430\u0440","CU":"\u041a\u0443\u0431\u0430","KW":"\u041a\u0443\u0432\u0435\u0439\u0442","CW":"\u041a\u044e\u0440\u0430\u0441\u0430\u043e","LA":"\u041b\u0430\u043e\u0441","LV":"\u041b\u0430\u0442\u0432\u0456\u044f","LS":"\u041b\u0435\u0441\u043e\u0442\u043e","LT":"\u041b\u0438\u0442\u0432\u0430","LR":"\u041b\u0456\u0431\u0435\u0440\u0456\u044f","LB":"\u041b\u0456\u0432\u0430\u043d","LY":"\u041b\u0456\u0432\u0456\u044f","LI":"\u041b\u0456\u0445\u0442\u0435\u043d\u0448\u0442\u0435\u0439\u043d","LU":"\u041b\u044e\u043a\u0441\u0435\u043c\u0431\u0443\u0440\u0491","MR":"\u041c\u0430\u0432\u0440\u0438\u0442\u0430\u043d\u0456\u044f","MU":"\u041c\u0430\u0432\u0440\u0456\u043a\u0456\u0439","MG":"\u041c\u0430\u0434\u0430\u0433\u0430\u0441\u043a\u0430\u0440","YT":"\u041c\u0430\u0439\u043e\u0442\u0442\u0430","MO":"\u041c\u0430\u043a\u0430\u043e, \u041e.\u0410.\u0420 \u041a\u0438\u0442\u0430\u044e","MW":"\u041c\u0430\u043b\u0430\u0432\u0456","MY":"\u041c\u0430\u043b\u0430\u0439\u0437\u0456\u044f","ML":"\u041c\u0430\u043b\u0456","MV":"\u041c\u0430\u043b\u044c\u0434\u0456\u0432\u0438","MT":"\u041c\u0430\u043b\u044c\u0442\u0430","MA":"\u041c\u0430\u0440\u043e\u043a\u043a\u043e","MQ":"\u041c\u0430\u0440\u0442\u0456\u043d\u0456\u043a\u0430","MH":"\u041c\u0430\u0440\u0448\u0430\u043b\u043b\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","MX":"\u041c\u0435\u043a\u0441\u0438\u043a\u0430","FM":"\u041c\u0456\u043a\u0440\u043e\u043d\u0435\u0437\u0456\u044f","MZ":"\u041c\u043e\u0437\u0430\u043c\u0431\u0456\u043a","MD":"\u041c\u043e\u043b\u0434\u043e\u0432\u0430","MC":"\u041c\u043e\u043d\u0430\u043a\u043e","MN":"\u041c\u043e\u043d\u0433\u043e\u043b\u0456\u044f","MS":"\u041c\u043e\u043d\u0442\u0441\u0435\u0440\u0440\u0430\u0442","MM":"\u041c\u02bc\u044f\u043d\u043c\u0430 (\u0411\u0456\u0440\u043c\u0430)","NA":"\u041d\u0430\u043c\u0456\u0431\u0456\u044f","NR":"\u041d\u0430\u0443\u0440\u0443","NP":"\u041d\u0435\u043f\u0430\u043b","NE":"\u041d\u0456\u0433\u0435\u0440","NG":"\u041d\u0456\u0433\u0435\u0440\u0456\u044f","NL":"\u041d\u0456\u0434\u0435\u0440\u043b\u0430\u043d\u0434\u0438","BQ":"\u041d\u0456\u0434\u0435\u0440\u043b\u0430\u043d\u0434\u0441\u044c\u043a\u0456 \u041a\u0430\u0440\u0438\u0431\u0441\u044c\u043a\u0456 \u043e\u0441\u0442\u0440\u043e\u0432\u0438","NI":"\u041d\u0456\u043a\u0430\u0440\u0430\u0491\u0443\u0430","DE":"\u041d\u0456\u043c\u0435\u0447\u0447\u0438\u043d\u0430","NU":"\u041d\u0456\u0443\u0435","NZ":"\u041d\u043e\u0432\u0430 \u0417\u0435\u043b\u0430\u043d\u0434\u0456\u044f","NC":"\u041d\u043e\u0432\u0430 \u041a\u0430\u043b\u0435\u0434\u043e\u043d\u0456\u044f","NO":"\u041d\u043e\u0440\u0432\u0435\u0491\u0456\u044f","AE":"\u041e\u0431\u02bc\u0454\u0434\u043d\u0430\u043d\u0456 \u0410\u0440\u0430\u0431\u0441\u044c\u043a\u0456 \u0415\u043c\u0456\u0440\u0430\u0442\u0438","OM":"\u041e\u043c\u0430\u043d","BV":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0411\u0443\u0432\u0435","IM":"\u041e\u0441\u0442\u0440\u0456\u0432 \u041c\u0435\u043d","NF":"\u041e\u0441\u0442\u0440\u0456\u0432 \u041d\u043e\u0440\u0444\u043e\u043b\u043a","CX":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0420\u0456\u0437\u0434\u0432\u0430","SH":"\u041e\u0441\u0442\u0440\u0456\u0432 \u0421\u0432\u044f\u0442\u043e\u0457 \u0404\u043b\u0435\u043d\u0438","HM":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u0413\u0435\u0440\u0434 \u0456 \u041c\u0430\u043a\u0434\u043e\u043d\u0430\u043b\u0434","CK":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u041a\u0443\u043a\u0430","PN":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u041f\u0456\u0442\u043a\u0435\u0440\u043d","TC":"\u041e\u0441\u0442\u0440\u043e\u0432\u0438 \u0422\u0435\u0440\u043a\u0441 \u0456 \u041a\u0430\u0439\u043a\u043e\u0441","PK":"\u041f\u0430\u043a\u0438\u0441\u0442\u0430\u043d","PW":"\u041f\u0430\u043b\u0430\u0443","PS":"\u041f\u0430\u043b\u0435\u0441\u0442\u0438\u043d\u0441\u044c\u043a\u0456 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u0457","PA":"\u041f\u0430\u043d\u0430\u043c\u0430","PG":"\u041f\u0430\u043f\u0443\u0430-\u041d\u043e\u0432\u0430 \u0490\u0432\u0456\u043d\u0435\u044f","PY":"\u041f\u0430\u0440\u0430\u0491\u0432\u0430\u0439","PE":"\u041f\u0435\u0440\u0443","GS":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0430 \u0414\u0436\u043e\u0440\u0434\u0436\u0456\u044f \u0442\u0430 \u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0456 \u0421\u0430\u043d\u0434\u0432\u0456\u0447\u0435\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","KR":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0430 \u041a\u043e\u0440\u0435\u044f","SS":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0438\u0439 \u0421\u0443\u0434\u0430\u043d","ZA":"\u041f\u0456\u0432\u0434\u0435\u043d\u043d\u043e-\u0410\u0444\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","KP":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0430 \u041a\u043e\u0440\u0435\u044f","MK":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0430 \u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0456\u044f","MP":"\u041f\u0456\u0432\u043d\u0456\u0447\u043d\u0456 \u041c\u0430\u0440\u0456\u0430\u043d\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","PL":"\u041f\u043e\u043b\u044c\u0449\u0430","PT":"\u041f\u043e\u0440\u0442\u0443\u0491\u0430\u043b\u0456\u044f","PR":"\u041f\u0443\u0435\u0440\u0442\u043e-\u0420\u0456\u043a\u043e","RE":"\u0420\u0435\u044e\u043d\u044c\u0439\u043e\u043d","RU":"\u0420\u043e\u0441\u0456\u044f","RW":"\u0420\u0443\u0430\u043d\u0434\u0430","RO":"\u0420\u0443\u043c\u0443\u043d\u0456\u044f","SV":"\u0421\u0430\u043b\u044c\u0432\u0430\u0434\u043e\u0440","WS":"\u0421\u0430\u043c\u043e\u0430","SM":"\u0421\u0430\u043d-\u041c\u0430\u0440\u0456\u043d\u043e","ST":"\u0421\u0430\u043d-\u0422\u043e\u043c\u0435 \u0456 \u041f\u0440\u0456\u043d\u0441\u0456\u043f\u0456","SA":"\u0421\u0430\u0443\u0434\u0456\u0432\u0441\u044c\u043a\u0430 \u0410\u0440\u0430\u0432\u0456\u044f","SC":"\u0421\u0435\u0439\u0448\u0435\u043b\u044c\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","BL":"\u0421\u0435\u043d-\u0411\u0430\u0440\u0442\u0435\u043b\u044c\u043c\u0456","MF":"\u0421\u0435\u043d-\u041c\u0430\u0440\u0442\u0435\u043d","PM":"\u0421\u0435\u043d-\u041f\u02bc\u0454\u0440 \u0456 \u041c\u0456\u043a\u0435\u043b\u043e\u043d","SN":"\u0421\u0435\u043d\u0435\u0433\u0430\u043b","VC":"\u0421\u0435\u043d\u0442-\u0412\u0456\u043d\u0441\u0435\u043d\u0442 \u0456 \u0490\u0440\u0435\u043d\u0430\u0434\u0456\u043d\u0438","KN":"\u0421\u0435\u043d\u0442-\u041a\u0456\u0442\u0441 \u0456 \u041d\u0435\u0432\u0456\u0441","LC":"\u0421\u0435\u043d\u0442-\u041b\u044e\u0441\u0456\u044f","RS":"\u0421\u0435\u0440\u0431\u0456\u044f","SY":"\u0421\u0438\u0440\u0456\u044f","SG":"\u0421\u0456\u043d\u0433\u0430\u043f\u0443\u0440","SX":"\u0421\u0456\u043d\u0442-\u041c\u0430\u0440\u0442\u0435\u043d","SK":"\u0421\u043b\u043e\u0432\u0430\u0447\u0447\u0438\u043d\u0430","SI":"\u0421\u043b\u043e\u0432\u0435\u043d\u0456\u044f","SB":"\u0421\u043e\u043b\u043e\u043c\u043e\u043d\u043e\u0432\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","SO":"\u0421\u043e\u043c\u0430\u043b\u0456","US":"\u0421\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0456 \u0428\u0442\u0430\u0442\u0438","SD":"\u0421\u0443\u0434\u0430\u043d","SR":"\u0421\u0443\u0440\u0456\u043d\u0430\u043c","SL":"\u0421\u044c\u0454\u0440\u0440\u0430-\u041b\u0435\u043e\u043d\u0435","TJ":"\u0422\u0430\u0434\u0436\u0438\u043a\u0438\u0441\u0442\u0430\u043d","TH":"\u0422\u0430\u0457\u043b\u0430\u043d\u0434","TW":"\u0422\u0430\u0439\u0432\u0430\u043d\u044c","TZ":"\u0422\u0430\u043d\u0437\u0430\u043d\u0456\u044f","TL":"\u0422\u0456\u043c\u043e\u0440-\u041b\u0435\u0448\u0442\u0456","TG":"\u0422\u043e\u0433\u043e","TK":"\u0422\u043e\u043a\u0435\u043b\u0430\u0443","TO":"\u0422\u043e\u043d\u0491\u0430","TT":"\u0422\u0440\u0456\u043d\u0456\u0434\u0430\u0434 \u0456 \u0422\u043e\u0431\u0430\u0491\u043e","TV":"\u0422\u0443\u0432\u0430\u043b\u0443","TN":"\u0422\u0443\u043d\u0456\u0441","TR":"\u0422\u0443\u0440\u0435\u0447\u0447\u0438\u043d\u0430","TM":"\u0422\u0443\u0440\u043a\u043c\u0435\u043d\u0456\u0441\u0442\u0430\u043d","UG":"\u0423\u0433\u0430\u043d\u0434\u0430","HU":"\u0423\u0433\u043e\u0440\u0449\u0438\u043d\u0430","UZ":"\u0423\u0437\u0431\u0435\u043a\u0438\u0441\u0442\u0430\u043d","UA":"\u0423\u043a\u0440\u0430\u0457\u043d\u0430","WF":"\u0423\u043e\u043b\u043b\u0456\u0441 \u0456 \u0424\u0443\u0442\u0443\u043d\u0430","UY":"\u0423\u0440\u0443\u0491\u0432\u0430\u0439","FO":"\u0424\u0430\u0440\u0435\u0440\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","FJ":"\u0424\u0456\u0434\u0436\u0456","PH":"\u0424\u0456\u043b\u0456\u043f\u043f\u0456\u043d\u0438","FI":"\u0424\u0456\u043d\u043b\u044f\u043d\u0434\u0456\u044f","FK":"\u0424\u043e\u043b\u043a\u043b\u0435\u043d\u0434\u0441\u044c\u043a\u0456 \u041e\u0441\u0442\u0440\u043e\u0432\u0438","FR":"\u0424\u0440\u0430\u043d\u0446\u0456\u044f","GF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0430 \u0490\u0432\u0456\u0430\u043d\u0430","PF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0430 \u041f\u043e\u043b\u0456\u043d\u0435\u0437\u0456\u044f","TF":"\u0424\u0440\u0430\u043d\u0446\u0443\u0437\u044c\u043a\u0456 \u041f\u0456\u0432\u0434\u0435\u043d\u043d\u0456 \u0422\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u0457","HR":"\u0425\u043e\u0440\u0432\u0430\u0442\u0456\u044f","CF":"\u0426\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u0430\u0444\u0440\u0438\u043a\u0430\u043d\u0441\u044c\u043a\u0430 \u0420\u0435\u0441\u043f\u0443\u0431\u043b\u0456\u043a\u0430","TD":"\u0427\u0430\u0434","CZ":"\u0427\u0435\u0445\u0456\u044f","CL":"\u0427\u0456\u043b\u0456","ME":"\u0427\u043e\u0440\u043d\u043e\u0433\u043e\u0440\u0456\u044f","CH":"\u0428\u0432\u0435\u0439\u0446\u0430\u0440\u0456\u044f","SE":"\u0428\u0432\u0435\u0446\u0456\u044f","SJ":"\u0428\u043f\u0456\u0446\u0431\u0435\u0440\u0433\u0435\u043d \u0442\u0430 \u042f\u043d-\u041c\u0430\u0454\u043d","LK":"\u0428\u0440\u0456-\u041b\u0430\u043d\u043a\u0430","JM":"\u042f\u043c\u0430\u0439\u043a\u0430","JP":"\u042f\u043f\u043e\u043d\u0456\u044f"} \ No newline at end of file diff --git a/scripts/download-country-names.js b/scripts/download-country-names.js new file mode 100644 index 00000000..4ee90cd4 --- /dev/null +++ b/scripts/download-country-names.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const chalk = require('chalk'); + +const src = path.resolve(__dirname, '../lang'); +const dest = path.resolve(__dirname, '../public/country'); +const files = fs.readdirSync(src); + +const getUrl = locale => + `https://raw.githubusercontent.com/umpirsky/country-list/master/data/${locale}/country.json`; + +const asyncForEach = async (array, callback) => { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +}; + +if (!fs.existsSync(dest)) { + fs.mkdirSync(dest); +} + +const download = async files => { + await asyncForEach(files, async file => { + const locale = file.replace('-', '_').replace('.json', ''); + + const filename = path.join(dest, file); + if (!fs.existsSync(filename)) { + await new Promise(resolve => { + https.get(getUrl(locale), res => { + console.log('Downloaded', chalk.greenBright('->'), filename); + resolve(res.pipe(fs.createWriteStream(filename))); + }); + }); + } + }); +}; + +download(files); From 7b5330c8a5461ccabd14c85bfb75fc8580854a63 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 11:45:19 -0700 Subject: [PATCH 21/71] Updated languages. --- lib/lang.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/lang.js b/lib/lang.js index 388c58e5..200ac893 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -1,5 +1,23 @@ import { format } from 'date-fns'; -import { enUS, nl, zhCN, tr, ru, de, ja, es, fr, da, sv, el, pt, ro, nb } from 'date-fns/locale'; +import { + enUS, + nl, + zhCN, + tr, + ru, + de, + ja, + es, + fr, + da, + sv, + el, + pt, + ro, + nb, + id, + uk, +} from 'date-fns/locale'; import enMessages from 'lang-compiled/en-US.json'; import nlMessages from 'lang-compiled/nl-NL.json'; import zhCNMessages from 'lang-compiled/zh-CN.json'; @@ -17,6 +35,8 @@ import foMessages from 'lang-compiled/fo-FO.json'; import ptMessages from 'lang-compiled/pt-PT.json'; import roMessages from 'lang-compiled/ro-RO.json'; import nbNOMessages from 'lang-compiled/nb-NO.json'; +import idMessages from 'lang-compiled/id-ID.json'; +import ukMessages from 'lang-compiled/uk-UA.json'; export const messages = { 'en-US': enMessages, @@ -36,6 +56,8 @@ export const messages = { 'pt-PT': ptMessages, 'ro-RO': roMessages, 'nb-NO': nbNOMessages, + 'id-ID': idMessages, + 'uk-UA': ukMessages, }; export const dateLocales = { @@ -56,6 +78,8 @@ export const dateLocales = { 'pt-PT': pt, 'ro-RO': ro, 'nb-NO': nb, + 'id-ID': id, + 'uk-UA': uk, }; export const menuOptions = [ @@ -67,6 +91,7 @@ export const menuOptions = [ { label: 'Føroyskt', value: 'fo-FO', display: 'fo' }, { label: 'Français', value: 'fr-FR', display: 'fr' }, { label: 'Ελληνικά', value: 'el-GR', display: 'el' }, + { label: 'Bahasa Indonesia', value: 'id-ID', display: 'id' }, { label: '日本語', value: 'ja-JP', display: 'ja' }, { label: 'Монгол', value: 'mn-MN', display: 'mn' }, { label: 'Nederlands', value: 'nl-NL', display: 'nl' }, @@ -76,6 +101,7 @@ export const menuOptions = [ { label: 'Română', value: 'ro-RO', display: 'ro' }, { label: 'Svenska', value: 'sv-SE', display: 'sv' }, { label: 'Türkçe', value: 'tr-TR', display: 'tr' }, + { label: 'українська', value: 'uk-UA', display: 'uk' }, ]; export function dateFormat(date, str, locale) { From 34098bd0b48d62d5772a36a195593118946ea263 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 18:05:46 -0700 Subject: [PATCH 22/71] Small prop change. --- components/common/MenuButton.js | 7 ++++--- components/settings/LanguageButton.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/common/MenuButton.js b/components/common/MenuButton.js index 4f3f2584..f3de66d0 100644 --- a/components/common/MenuButton.js +++ b/components/common/MenuButton.js @@ -9,7 +9,8 @@ export default function MenuButton({ icon, value, options, - menuClassname, + buttonClassName, + menuClassName, menuPosition = 'bottom', menuAlign = 'right', onSelect, @@ -38,7 +39,7 @@ export default function MenuButton({
{showMenu && ( } options={menuOptions} value={locale} - menuClassname={styles.menu} + menuClassName={styles.menu} renderValue={option => option?.display} onSelect={handleSelect} /> From fc22f5969ce586995256b7c83b59e2393f5442c1 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 19:07:56 -0700 Subject: [PATCH 23/71] Added IP address ignore list. --- package.json | 2 +- pages/api/collect.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c6e899e..c5553e41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.74.0", + "version": "0.75.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/pages/api/collect.js b/pages/api/collect.js index e6b3e0ca..4f01b225 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -3,12 +3,22 @@ import { savePageView, saveEvent } from 'lib/queries'; import { useCors, useSession } from 'lib/middleware'; import { ok, badRequest } from 'lib/response'; import { createToken } from 'lib/crypto'; +import { getIpAddress } from '../../lib/request'; export default async (req, res) => { if (isBot(req.headers['user-agent'])) { return ok(res); } + if (process.env.IGNORE_IP) { + const ips = process.env.IGNORE_IP.split(',').map(n => n.trim()); + const ip = getIpAddress(req); + + if (ips.includes(ip)) { + return ok(res); + } + } + await useCors(req, res); await useSession(req, res); From 4cafa68e23d4fe693b095d9c4a4b4b1e0b6aab9c Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 21:54:21 -0700 Subject: [PATCH 24/71] Add force SSL check. --- hooks/useForceSSL.js | 14 ++++++++++++++ next.config.js | 4 +--- package.json | 2 +- pages/_app.js | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 hooks/useForceSSL.js diff --git a/hooks/useForceSSL.js b/hooks/useForceSSL.js new file mode 100644 index 00000000..b9d95e19 --- /dev/null +++ b/hooks/useForceSSL.js @@ -0,0 +1,14 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; + +export default function useForceSSL(enabled) { + const router = useRouter(); + + useEffect(() => { + if (enabled && typeof window !== 'undefined' && /^http:\/\//.test(location.href)) { + router.push(location.href.replace(/^http:\/\//, 'https://')); + } + }, [enabled]); + + return null; +} diff --git a/next.config.js b/next.config.js index c1e31d7b..9d7d8e99 100644 --- a/next.config.js +++ b/next.config.js @@ -4,9 +4,7 @@ const pkg = require('./package.json'); module.exports = { env: { VERSION: pkg.version, - }, - serverRuntimeConfig: { - PROJECT_ROOT: __dirname, + FORCE_SSL: !!process.env.FORCE_SSL, }, webpack(config) { config.module.rules.push({ diff --git a/package.json b/package.json index c5553e41..f5fe6909 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.75.0", + "version": "0.76.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/pages/_app.js b/pages/_app.js index 9aad4339..2849d2f0 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -3,6 +3,7 @@ import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; import { useStore } from 'redux/store'; import useLocale from 'hooks/useLocale'; +import useForceSSL from 'hooks/useForceSSL'; import { messages } from 'lib/lang'; import 'styles/variables.css'; import 'styles/bootstrap-grid.css'; @@ -21,6 +22,7 @@ const Intl = ({ children }) => { }; export default function App({ Component, pageProps }) { + useForceSSL(process.env.FORCE_SSL); const store = useStore(); return ( From ca8a6fe049c1b0adb599d0ee68bcecdfe22a8aaf Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 3 Oct 2020 22:36:51 -0700 Subject: [PATCH 25/71] Added error message component. Update fetch hook. --- assets/exclamation-triangle.svg | 1 + components/common/ErrorMessage.js | 14 ++++++++++++++ components/common/ErrorMessage.module.css | 13 +++++++++++++ components/metrics/MetricsBar.js | 9 +++++---- components/metrics/MetricsTable.js | 10 ++++++---- components/metrics/WebsiteChart.js | 4 +++- hooks/useFetch.js | 8 +++++++- package.json | 2 +- 8 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 assets/exclamation-triangle.svg create mode 100644 components/common/ErrorMessage.js create mode 100644 components/common/ErrorMessage.module.css diff --git a/assets/exclamation-triangle.svg b/assets/exclamation-triangle.svg new file mode 100644 index 00000000..46bef5bc --- /dev/null +++ b/assets/exclamation-triangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/common/ErrorMessage.js b/components/common/ErrorMessage.js new file mode 100644 index 00000000..5747f226 --- /dev/null +++ b/components/common/ErrorMessage.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import Icon from './Icon'; +import Exclamation from 'assets/exclamation-triangle.svg'; +import styles from './ErrorMessage.module.css'; + +export default function ErrorMessage() { + return ( +
+ } className={styles.icon} size="large" /> + +
+ ); +} diff --git a/components/common/ErrorMessage.module.css b/components/common/ErrorMessage.module.css new file mode 100644 index 00000000..232b5f84 --- /dev/null +++ b/components/common/ErrorMessage.module.css @@ -0,0 +1,13 @@ +.error { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin: auto; + display: flex; + z-index: 1; +} + +.icon { + margin-right: 10px; +} diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index f5d888d4..b7a47a10 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Loading from 'components/common/Loading'; +import ErrorMessage from 'components/common/ErrorMessage'; import useFetch from 'hooks/useFetch'; import useDateRange from 'hooks/useDateRange'; import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format'; @@ -17,7 +18,7 @@ export default function MetricsBar({ websiteId, token, className }) { query: { url }, } = usePageQuery(); - const { data } = useFetch( + const { data, error, loading } = useFetch( `/api/website/${websiteId}/metrics`, { start_at: +startDate, @@ -40,9 +41,9 @@ export default function MetricsBar({ websiteId, token, className }) { return (
- {!data ? ( - - ) : ( + {!data && loading && } + {error && } + {data && !error && ( <> } diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 3ac8a395..6850a3bf 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -13,6 +13,7 @@ import { formatNumber, formatLongNumber } from 'lib/format'; import useDateRange from 'hooks/useDateRange'; import usePageQuery from 'hooks/usePageQuery'; import styles from './MetricsTable.module.css'; +import ErrorMessage from '../common/ErrorMessage'; export default function MetricsTable({ websiteId, @@ -36,7 +37,7 @@ export default function MetricsTable({ query: { url }, } = usePageQuery(); - const { data } = useFetch( + const { data, loading, error } = useFetch( `/api/website/${websiteId}/rankings`, { type, @@ -61,7 +62,7 @@ export default function MetricsTable({ return items; } return []; - }, [data, dataFilter, filterOptions]); + }, [data, error, dataFilter, filterOptions]); const handleSetFormat = () => setFormat(state => !state); @@ -86,8 +87,9 @@ export default function MetricsTable({ return (
- {!data && } - {data && ( + {!data && loading && } + {error && } + {data && !error && ( <>
{title}
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index ea86ad3e..6a07afe5 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -13,6 +13,7 @@ import usePageQuery from 'hooks/usePageQuery'; import { getDateArray, getDateLength } from 'lib/date'; import Times from 'assets/times.svg'; import styles from './WebsiteChart.module.css'; +import ErrorMessage from '../common/ErrorMessage'; export default function WebsiteChart({ websiteId, @@ -31,7 +32,7 @@ export default function WebsiteChart({ query: { url }, } = usePageQuery(); - const { data, loading } = useFetch( + const { data, loading, error } = useFetch( `/api/website/${websiteId}/pageviews`, { start_at: +startDate, @@ -83,6 +84,7 @@ export default function WebsiteChart({
+ {error && } = 400) { + setError(data); + setData(null); + } else { + setData(data); + } + setStatus(status); onDataLoad(data); } catch (e) { diff --git a/package.json b/package.json index f5fe6909..0d602350 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.76.0", + "version": "0.77.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From 195eb06a02916f3bc93f471264d074b6abfc639b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 4 Oct 2020 22:27:59 -0700 Subject: [PATCH 26/71] Add domain filter to tracker. --- package.json | 2 +- tracker/index.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0d602350..c21f3076 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.77.0", + "version": "0.78.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/tracker/index.js b/tracker/index.js index 154e0034..df4c5216 100644 --- a/tracker/index.js +++ b/tracker/index.js @@ -19,8 +19,19 @@ import { removeTrailingSlash } from '../lib/url'; const autoTrack = attr('data-auto-track') !== 'false'; const dnt = attr('data-do-not-track'); const useCache = attr('data-cache'); + const domains = attr('data-domains'); - if (!script || (dnt && doNotTrack())) return; + if ( + !script || + (dnt && doNotTrack()) || + (domains && + !domains + .split(',') + .map(n => n.trim()) + .includes(hostname)) + ) { + return; + } const root = hostUrl ? removeTrailingSlash(hostUrl) From cc612cb831b8f13df2686303de32a57dca45dff0 Mon Sep 17 00:00:00 2001 From: Frans Allen Date: Mon, 5 Oct 2020 14:45:24 +0700 Subject: [PATCH 27/71] Implement Cache-Control header for umami.js --- next.config.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/next.config.js b/next.config.js index c1e31d7b..e386c04e 100644 --- a/next.config.js +++ b/next.config.js @@ -19,4 +19,17 @@ module.exports = { return config; }, + async headers() { + return [ + { + source: '/umami.js', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=2592000', // 30 days + }, + ], + }, + ] + }, }; From d09d469bf4e1bc3b81a23cd7260c28689f53b67b Mon Sep 17 00:00:00 2001 From: Bram <24355695+brams-dev@users.noreply.github.com> Date: Wed, 7 Oct 2020 11:54:45 +0000 Subject: [PATCH 28/71] Add missing dutch translations --- lang/nl-NL.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lang/nl-NL.json b/lang/nl-NL.json index 148fd32e..54f7894f 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -7,22 +7,22 @@ "button.copy-to-clipboard": "Kopiëer naar klembord", "button.date-range": "Datumbereik", "button.delete": "Verwijderen", - "button.dismiss": "Dismiss", + "button.dismiss": "Negeren", "button.edit": "Bewerken", "button.login": "Inloggen", "button.more": "Toon meer", "button.refresh": "Vernieuwen", - "button.reset": "Reset", + "button.reset": "Resetten", "button.save": "Opslaan", "button.single-day": "Enkele dag", "button.view-details": "Meer details", - "label.accounts": "Accounts", + "label.accounts": "Gebruikers", "label.administrator": "Administrator", "label.confirm-password": "Wachtwoord bevestigen", "label.current-password": "Huidig wachtwoord", "label.custom-range": "Aangepast bereik", - "label.dashboard": "Dashboard", - "label.default-date-range": "Default date range", + "label.dashboard": "Overzicht", + "label.default-date-range": "Standaard bereik", "label.domain": "Domein", "label.enable-share-url": "Sta delen via openbare URL toe", "label.invalid": "Ongeldig", @@ -41,7 +41,7 @@ "label.this-month": "Deze maand", "label.this-week": "Deze week", "label.this-year": "Dit jaar", - "label.timezone": "Timezone", + "label.timezone": "Tijdzone", "label.today": "Vandaag", "label.unknown": "Onbekend", "label.username": "Gebruikersnaam", @@ -55,7 +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.new-version-available": "Een nieuwe versie van umami {version} is beschikbaar!", "message.no-data-available": "Geen gegevens beschikbaar.", "message.no-websites-configured": "Je hebt geen websites ingesteld.", "message.page-not-found": "Pagina niet gevonden.", From dca51050e8b8aa91c1271af6567bca29af4716e7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 7 Oct 2020 08:31:44 -0700 Subject: [PATCH 29/71] Removed disconnect code. --- lib/queries.js | 10 +++------- package.json | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/queries.js b/lib/queries.js index 7c0bb6e7..3b138faa 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -16,13 +16,9 @@ export function getDatabase() { } export async function runQuery(query) { - return query - .catch(e => { - throw e; - }) - .finally(async () => { - await prisma.$disconnect(); - }); + return query.catch(e => { + throw e; + }); } export async function rawQuery(query, params = []) { diff --git a/package.json b/package.json index c21f3076..aca8f8c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.78.0", + "version": "0.80.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From b4ea70a67c64b54f70456fa979e8e4cae55dd32c Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 7 Oct 2020 18:24:53 -0700 Subject: [PATCH 30/71] Fix event colors. Updated packages. --- components/metrics/EventsChart.js | 4 +- lib/constants.js | 10 +- package.json | 16 +- yarn.lock | 268 +++++++++++++++--------------- 4 files changed, 148 insertions(+), 150 deletions(-) diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js index 113c6f56..0a348a29 100644 --- a/components/metrics/EventsChart.js +++ b/components/metrics/EventsChart.js @@ -5,8 +5,8 @@ import { getDateArray, getDateLength } from 'lib/date'; import useFetch from 'hooks/useFetch'; import useDateRange from 'hooks/useDateRange'; import useTimezone from 'hooks/useTimezone'; +import usePageQuery from 'hooks/usePageQuery'; import { EVENT_COLORS } from 'lib/constants'; -import usePageQuery from '../../hooks/usePageQuery'; export default function EventsChart({ websiteId, token }) { const [dateRange] = useDateRange(websiteId); @@ -44,7 +44,7 @@ export default function EventsChart({ websiteId, token }) { }); return Object.keys(map).map((key, index) => { - const color = tinycolor(EVENT_COLORS[index]); + const color = tinycolor(EVENT_COLORS[index % EVENT_COLORS.length]); return { label: key, data: map[key], diff --git a/lib/constants.js b/lib/constants.js index 87e0cf1d..85515481 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -42,9 +42,13 @@ export const EVENT_COLORS = [ '#44b556', '#e68619', '#e34850', - '#1b959a', - '#d83790', - '#85d044', + '#f7bd12', + '#01bad7', + '#6734bc', + '#89c541', + '#ffc301', + '#ec1562', + '#ffec16', ]; export const DEFAULT_DATE_RANGE = '24hour'; diff --git a/package.json b/package.json index aca8f8c0..8f9edabb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.80.0", + "version": "0.81.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -56,7 +56,7 @@ } }, "dependencies": { - "@prisma/client": "2.8.0", + "@prisma/client": "2.8.1", "@reduxjs/toolkit": "^1.4.0", "bcrypt": "^5.0.0", "chalk": "^4.1.0", @@ -65,7 +65,7 @@ "cookie": "^0.4.1", "cors": "^2.8.5", "date-fns": "^2.16.1", - "date-fns-tz": "^1.0.10", + "date-fns-tz": "^1.0.11", "detect-browser": "^5.1.1", "dotenv": "^8.2.0", "formik": "^2.1.7", @@ -73,9 +73,9 @@ "is-localhost-ip": "^1.4.0", "isbot-fast": "^1.2.0", "jose": "^2.0.2", - "maxmind": "^4.2.0", + "maxmind": "^4.3.0", "moment-timezone": "^0.5.31", - "next": "^9.5.3", + "next": "^9.5.4", "react": "^16.13.1", "react-dom": "^16.13.1", "react-intl": "^5.8.4", @@ -91,11 +91,11 @@ "thenby": "^1.3.4", "timezone-support": "^2.0.2", "tinycolor2": "^1.4.2", - "uuid": "^8.3.0" + "uuid": "^8.3.1" }, "devDependencies": { "@formatjs/cli": "^2.13.0", - "@prisma/cli": "2.8.0", + "@prisma/cli": "2.8.1", "@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-replace": "^2.3.3", @@ -106,7 +106,7 @@ "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-prettier": "^3.1.3", - "eslint-plugin-react": "^7.21.2", + "eslint-plugin-react": "^7.21.3", "eslint-plugin-react-hooks": "^4.1.2", "extract-react-intl-messages": "^4.1.1", "husky": "^4.3.0", diff --git a/yarn.lock b/yarn.lock index c38489ea..df03a2ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -398,7 +398,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@7.10.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== @@ -431,7 +431,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@7.11.0", "@babel/plugin-proposal-optional-chaining@^7.11.0": +"@babel/plugin-proposal-optional-chaining@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== @@ -498,7 +498,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.10.4": +"@babel/plugin-syntax-jsx@7.10.4", "@babel/plugin-syntax-jsx@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== @@ -1159,10 +1159,20 @@ intl-messageformat-parser "^6.0.7" typescript "^4.0" -"@next/react-dev-overlay@9.5.3": - version "9.5.3" - resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.3.tgz#3275301f08045ecc709e3273031973a1f5e81427" - integrity sha512-R2ZAyFjHHaMTBVi19ZZNRJNXiwn46paRi7EZvKNvMxbrzBcUYtSFj/edU3jQoF1UOcC6vGeMhtPqH55ONrIjCQ== +"@next/env@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-9.5.4.tgz#950f3370151a940ecac6e7e19cf125e6113e101e" + integrity sha512-uGnUO68/u9C8bqHj5obIvyGRDqe/jh1dFSLx03mJmlESjcCmV4umXYJOnt3XzU1VhVntSE+jUZtnS5bjYmmLfQ== + +"@next/polyfill-module@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-9.5.4.tgz#35ea31ce5f6bbf0ac31aac483b60d4ba17a79861" + integrity sha512-GA2sW7gs33s7RGPFqkMiT9asYpaV/Hhw9+XM9/UlPrkNdTaxZWaPa2iHgmqJ7k6OHiOmy+CBLFrUBgzqKNhs3Q== + +"@next/react-dev-overlay@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.4.tgz#7d88a710d23021020cca213bc77106df18950b2b" + integrity sha512-tYvNmOQ0inykSvcimkTiONMv4ZyFB2G2clsy9FKLLRZ2OA+Jiov6T7Pq6YpKbBwTLu/BQGVc7Qn4BZ5CDHR8ig== dependencies: "@babel/code-frame" "7.10.4" ally.js "1.4.1" @@ -1175,10 +1185,10 @@ stacktrace-parser "0.1.10" strip-ansi "6.0.0" -"@next/react-refresh-utils@9.5.3": - version "9.5.3" - resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.3.tgz#a14fb6489d412b201b98aa44716fb8727ca4c6ae" - integrity sha512-W3VKOqbg+4Kw+k6M/SODf+WIzwcx60nAemGV1nNPa/yrDtAS2YcJfqiswrJ3+2nJHzqefAFWn4XOfM0fy8ww2Q== +"@next/react-refresh-utils@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.4.tgz#3bfe067f0cfc717f079482d956211708c9e81126" + integrity sha512-TPhEiYxK5YlEuzVuTzgZiDN7SDh4drvUAqsO9Yccd8WLcfYqOLRN2fCALremW5mNLAZQZW3iFgW8PW8Gckq4EQ== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -1201,20 +1211,27 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@npmcli/move-file@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" + integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== + dependencies: + mkdirp "^1.0.4" + "@panva/asn1.js@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== -"@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/cli@2.8.1": + version "2.8.1" + resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.8.1.tgz#fdfeb56857f2fb08ec9769537279745749effcf7" + integrity sha512-mEGJplClGnXI5ki4R0Xq8nq62zcyu7Wzdhybege+cFPZFUbFj0petaPZl/tqta9o5neDyvpU/lDgOwef6mwySg== -"@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== +"@prisma/client@2.8.1": + version "2.8.1" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.8.1.tgz#6fe87968eed42901cf76c623985222ebc318c292" + integrity sha512-apt6ioi2euOZA1O9mPA8AMRjPoPECsva76gMCcHCVgHvhkMNpFkcbn+UTkErJYrTgcRR7CPQt4D+fw8pkAHfjA== dependencies: pkg-up "^3.1.0" @@ -1816,7 +1833,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.4: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== @@ -2351,28 +2368,27 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -cacache@13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" - integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== +cacache@15.0.5: + version "15.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" + integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== dependencies: - chownr "^1.1.2" - figgy-pudding "^3.5.1" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" fs-minipass "^2.0.0" glob "^7.1.4" - graceful-fs "^4.2.2" infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.0.0" + lru-cache "^6.0.0" + minipass "^3.1.1" minipass-collect "^1.0.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.2" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - p-map "^3.0.0" + mkdirp "^1.0.3" + p-map "^4.0.0" promise-inflight "^1.0.1" - rimraf "^2.7.1" - ssri "^7.0.0" + rimraf "^3.0.2" + ssri "^8.0.0" + tar "^6.0.2" unique-filename "^1.1.1" cacache@^12.0.2: @@ -2568,7 +2584,7 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1, chownr@^1.1.2: +chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2649,15 +2665,6 @@ clone-deep@^0.2.4: lazy-cache "^1.0.3" shallow-clone "^0.1.2" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -2975,24 +2982,23 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.5.3.tgz#95ac16468e1adcd95c844729e0bb167639eb0bcf" - integrity sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw== +css-loader@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== dependencies: - camelcase "^5.3.1" + camelcase "^6.0.0" cssesc "^3.0.0" icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.27" + loader-utils "^2.0.0" + postcss "^7.0.32" postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" + postcss-modules-local-by-default "^3.0.3" postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.3" - schema-utils "^2.6.6" - semver "^6.3.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" css-prefers-color-scheme@^3.1.1: version "3.1.1" @@ -3212,10 +3218,10 @@ data-uri-to-buffer@3.0.0: dependencies: buffer-from "^1.1.1" -date-fns-tz@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.10.tgz#30fef0038f80534fddd8e133a6b8ca55ba313748" - integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg== +date-fns-tz@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.11.tgz#e7d9ceea5ff29280e0ed11a436ac01e10bbc5754" + integrity sha512-nrgEo+rCw1g5KFsIfcnaAIMPg5l6rV5fwlfoPeiGpgctUPiEVI1KNyDY0HXl/LWXpPhTPK4e6SInuPA+bZKBDg== date-fns@^2.16.1: version "2.16.1" @@ -3699,10 +3705,10 @@ eslint-plugin-react-hooks@^4.1.2: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz#2eb53731d11c95826ef7a7272303eabb5c9a271e" integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg== -eslint-plugin-react@^7.21.2: - version "7.21.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.2.tgz#3bd5d2c4c36d5a0428d0d6dda301ac9a84d681b2" - integrity sha512-j3XKvrK3rpBzveKFbgAeGsWb9uz6iUOrR0jixRfjwdFeGSRsXvVTFtHDQYCjsd1/6Z/xvb8Vy3LiI5Reo7fDrg== +eslint-plugin-react@^7.21.3: + version "7.21.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.3.tgz#71655d2af5155b19285ec929dd2cdc67a4470b52" + integrity sha512-OI4GwTCqyIb4ipaOEGLWdaOHCXZZydStAsBEPB2e1ZfNM37bojpgO1BoOQbFb0eLVz3QLDx7b+6kYcrxCuJfhw== dependencies: array-includes "^3.1.1" array.prototype.flatmap "^1.2.3" @@ -4418,7 +4424,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -5230,6 +5236,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klona@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + known-css-properties@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" @@ -5498,7 +5509,7 @@ loud-rejection@*, loud-rejection@^2.2.0: currently-unhandled "^0.4.1" signal-exit "^3.0.2" -lru-cache@6.0.0: +lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== @@ -5580,11 +5591,12 @@ 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.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.2.0.tgz#912e5ec4a961807d20d7fb541160aeb5ea802c1c" - integrity sha512-TADiE11Q10IjvLtlo05tTD52xLqfCJMhE3eYJHmpYIKg668STi/fQZGH9X3FpqpIP/2WPgKFxf899awFvfMtQA== +maxmind@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/maxmind/-/maxmind-4.3.0.tgz#0e04f00503e3693615efe848e94ec959500d7573" + integrity sha512-qYdCsoivX7xGW8L5UhjZNxx57odwzmzFPtSt3YvGwW8moSKoIwnCq2NbmfHdmqYzj5ffzSzxKM3lqURN8INyzA== dependencies: + mmdb-lib "1.2.0" tiny-lru "7.0.6" md5.js@^1.3.4: @@ -5865,11 +5877,16 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mkdirp@^1.0.3: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mmdb-lib@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mmdb-lib/-/mmdb-lib-1.2.0.tgz#0ecd93f4942f65a2d09be0502fa9126939606727" + integrity sha512-3XYebkStxqCgWJjsmT9FCaE19Yi4Tvs2SBPKhUks3rJJh52oF1AKAd9kei+LTutud3a6RCZ0o2Um96Fn7o3zVA== + moment-timezone@^0.5.31: version "0.5.31" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05" @@ -5957,7 +5974,7 @@ neo-async@2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== -neo-async@^2.5.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -5967,22 +5984,21 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -next@^9.5.3: - version "9.5.3" - resolved "https://registry.yarnpkg.com/next/-/next-9.5.3.tgz#7af5270631f98d330a7f75a6e8e1ac202aa155e2" - integrity sha512-DGrpTNGV2RNMwLaSzpgbkbaUuVk30X71/roXHS10isSXo2Gm+qWcjonDyOxf1KmOvHZRHA/Fa+LaAR7ysdYS3A== +next@^9.5.4: + version "9.5.4" + resolved "https://registry.yarnpkg.com/next/-/next-9.5.4.tgz#3c6aa3fd38ff1711e956ea2b6833475e0262ec35" + integrity sha512-dicsJSxiUFcRjeZ/rNMAO3HS5ttFFuRHhdAn5g7lHnWUZ3MnEX4ggBIihaoUr6qu2So9KoqUPXpS91MuSXUmBw== dependencies: "@ampproject/toolbox-optimizer" "2.6.0" "@babel/code-frame" "7.10.4" "@babel/core" "7.7.7" "@babel/plugin-proposal-class-properties" "7.10.4" "@babel/plugin-proposal-export-namespace-from" "7.10.4" - "@babel/plugin-proposal-nullish-coalescing-operator" "7.10.4" "@babel/plugin-proposal-numeric-separator" "7.10.4" "@babel/plugin-proposal-object-rest-spread" "7.11.0" - "@babel/plugin-proposal-optional-chaining" "7.11.0" "@babel/plugin-syntax-bigint" "7.8.3" "@babel/plugin-syntax-dynamic-import" "7.8.3" + "@babel/plugin-syntax-jsx" "7.10.4" "@babel/plugin-transform-modules-commonjs" "7.10.4" "@babel/plugin-transform-runtime" "7.11.5" "@babel/preset-env" "7.11.5" @@ -5991,19 +6007,20 @@ next@^9.5.3: "@babel/preset-typescript" "7.10.4" "@babel/runtime" "7.11.2" "@babel/types" "7.11.5" - "@next/react-dev-overlay" "9.5.3" - "@next/react-refresh-utils" "9.5.3" + "@next/env" "9.5.4" + "@next/polyfill-module" "9.5.4" + "@next/react-dev-overlay" "9.5.4" + "@next/react-refresh-utils" "9.5.4" ast-types "0.13.2" - babel-plugin-syntax-jsx "6.18.0" babel-plugin-transform-define "2.0.0" babel-plugin-transform-react-remove-prop-types "0.4.24" browserslist "4.13.0" buffer "5.6.0" - cacache "13.0.1" + cacache "15.0.5" caniuse-lite "^1.0.30001113" chokidar "2.1.8" crypto-browserify "3.12.0" - css-loader "3.5.3" + css-loader "4.3.0" cssnano-simple "1.2.0" find-cache-dir "3.3.1" jest-worker "24.9.0" @@ -6020,15 +6037,15 @@ next@^9.5.3: react-is "16.13.1" react-refresh "0.8.3" resolve-url-loader "3.1.1" - sass-loader "8.0.2" - schema-utils "2.6.6" + sass-loader "10.0.2" + schema-utils "2.7.1" stream-browserify "3.0.0" style-loader "1.2.1" styled-jsx "3.3.0" use-subscription "1.4.1" vm-browserify "1.1.2" watchpack "2.0.0-beta.13" - web-vitals "0.2.1" + web-vitals "0.2.4" webpack "4.44.1" webpack-sources "1.4.3" @@ -6401,13 +6418,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -6837,7 +6847,7 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" -postcss-modules-local-by-default@^3.0.2: +postcss-modules-local-by-default@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== @@ -7022,7 +7032,7 @@ postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -7054,7 +7064,7 @@ postcss@7.0.32: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.34" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== @@ -7701,7 +7711,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7793,16 +7803,16 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== +sass-loader@10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.2.tgz#c7b73010848b264792dd45372eea0b87cba4401e" + integrity sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg== dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" + klona "^2.0.3" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^2.7.1" + semver "^7.3.2" sax@^1.2.4, sax@~1.2.4: version "1.2.4" @@ -7825,7 +7835,7 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6: +schema-utils@*, schema-utils@2.7.1, schema-utils@^2.6.6, schema-utils@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -7834,14 +7844,6 @@ schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@2.6.6: - version "2.6.6" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c" - integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA== - dependencies: - ajv "^6.12.0" - ajv-keywords "^3.4.1" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -7871,7 +7873,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.2: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -7926,13 +7928,6 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - shallow-equal@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" @@ -8155,12 +8150,11 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" -ssri@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" - integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g== +ssri@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" + integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== dependencies: - figgy-pudding "^3.5.1" minipass "^3.1.1" stable@^0.1.8: @@ -8619,7 +8613,7 @@ tar@^4.4.2: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@^6.0.5: +tar@^6.0.2, tar@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== @@ -9120,10 +9114,10 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" - integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== +uuid@^8.3.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: version "2.1.1" @@ -9215,10 +9209,10 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" -web-vitals@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.1.tgz#60782fa690243fe35613759a0c26431f57ba7b2d" - integrity sha512-2pdRlp6gJpOCg0oMMqwFF0axjk5D9WInc09RSYtqFgPXQ15+YKNQ7YnBBEqAL5jvmfH9WvoXDMb8DHwux7pIew== +web-vitals@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511" + integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg== webidl-conversions@^4.0.2: version "4.0.2" From 5f5335ed1e8e5077638519d4dae4e49a49294327 Mon Sep 17 00:00:00 2001 From: Florian Kapfenberger Date: Thu, 8 Oct 2020 10:27:53 +0200 Subject: [PATCH 31/71] feat: add link to changelog when clicking version --- components/layout/Footer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/layout/Footer.js b/components/layout/Footer.js index 6fd0a46c..62472f4b 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -24,7 +24,11 @@ export default function Footer() { }} />
-
{`v${current}`}
+
+ + {`v${current}`} + +
); From 12b047996d1b910cfd078ac7009041f255b67045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2020 20:40:22 +0000 Subject: [PATCH 32/71] Bump next from 9.5.3 to 9.5.4 Bumps [next](https://github.com/vercel/next.js) from 9.5.3 to 9.5.4. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v9.5.3...v9.5.4) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 214 ++++++++++++++++++++++++--------------------------- 2 files changed, 102 insertions(+), 114 deletions(-) diff --git a/package.json b/package.json index aca8f8c0..0ab5c084 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "jose": "^2.0.2", "maxmind": "^4.2.0", "moment-timezone": "^0.5.31", - "next": "^9.5.3", + "next": "^9.5.4", "react": "^16.13.1", "react-dom": "^16.13.1", "react-intl": "^5.8.4", diff --git a/yarn.lock b/yarn.lock index c38489ea..641dd5de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -398,7 +398,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@7.10.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== @@ -431,7 +431,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@7.11.0", "@babel/plugin-proposal-optional-chaining@^7.11.0": +"@babel/plugin-proposal-optional-chaining@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== @@ -498,7 +498,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.10.4": +"@babel/plugin-syntax-jsx@7.10.4", "@babel/plugin-syntax-jsx@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== @@ -1159,10 +1159,20 @@ intl-messageformat-parser "^6.0.7" typescript "^4.0" -"@next/react-dev-overlay@9.5.3": - version "9.5.3" - resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.3.tgz#3275301f08045ecc709e3273031973a1f5e81427" - integrity sha512-R2ZAyFjHHaMTBVi19ZZNRJNXiwn46paRi7EZvKNvMxbrzBcUYtSFj/edU3jQoF1UOcC6vGeMhtPqH55ONrIjCQ== +"@next/env@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-9.5.4.tgz#950f3370151a940ecac6e7e19cf125e6113e101e" + integrity sha512-uGnUO68/u9C8bqHj5obIvyGRDqe/jh1dFSLx03mJmlESjcCmV4umXYJOnt3XzU1VhVntSE+jUZtnS5bjYmmLfQ== + +"@next/polyfill-module@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-9.5.4.tgz#35ea31ce5f6bbf0ac31aac483b60d4ba17a79861" + integrity sha512-GA2sW7gs33s7RGPFqkMiT9asYpaV/Hhw9+XM9/UlPrkNdTaxZWaPa2iHgmqJ7k6OHiOmy+CBLFrUBgzqKNhs3Q== + +"@next/react-dev-overlay@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.4.tgz#7d88a710d23021020cca213bc77106df18950b2b" + integrity sha512-tYvNmOQ0inykSvcimkTiONMv4ZyFB2G2clsy9FKLLRZ2OA+Jiov6T7Pq6YpKbBwTLu/BQGVc7Qn4BZ5CDHR8ig== dependencies: "@babel/code-frame" "7.10.4" ally.js "1.4.1" @@ -1175,10 +1185,10 @@ stacktrace-parser "0.1.10" strip-ansi "6.0.0" -"@next/react-refresh-utils@9.5.3": - version "9.5.3" - resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.3.tgz#a14fb6489d412b201b98aa44716fb8727ca4c6ae" - integrity sha512-W3VKOqbg+4Kw+k6M/SODf+WIzwcx60nAemGV1nNPa/yrDtAS2YcJfqiswrJ3+2nJHzqefAFWn4XOfM0fy8ww2Q== +"@next/react-refresh-utils@9.5.4": + version "9.5.4" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.4.tgz#3bfe067f0cfc717f079482d956211708c9e81126" + integrity sha512-TPhEiYxK5YlEuzVuTzgZiDN7SDh4drvUAqsO9Yccd8WLcfYqOLRN2fCALremW5mNLAZQZW3iFgW8PW8Gckq4EQ== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -1201,6 +1211,13 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@npmcli/move-file@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" + integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== + dependencies: + mkdirp "^1.0.4" + "@panva/asn1.js@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" @@ -1816,7 +1833,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.4: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== @@ -2351,28 +2368,27 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -cacache@13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" - integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== +cacache@15.0.5: + version "15.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" + integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== dependencies: - chownr "^1.1.2" - figgy-pudding "^3.5.1" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" fs-minipass "^2.0.0" glob "^7.1.4" - graceful-fs "^4.2.2" infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.0.0" + lru-cache "^6.0.0" + minipass "^3.1.1" minipass-collect "^1.0.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.2" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - p-map "^3.0.0" + mkdirp "^1.0.3" + p-map "^4.0.0" promise-inflight "^1.0.1" - rimraf "^2.7.1" - ssri "^7.0.0" + rimraf "^3.0.2" + ssri "^8.0.0" + tar "^6.0.2" unique-filename "^1.1.1" cacache@^12.0.2: @@ -2568,7 +2584,7 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1, chownr@^1.1.2: +chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2649,15 +2665,6 @@ clone-deep@^0.2.4: lazy-cache "^1.0.3" shallow-clone "^0.1.2" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -2975,24 +2982,23 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.5.3.tgz#95ac16468e1adcd95c844729e0bb167639eb0bcf" - integrity sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw== +css-loader@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== dependencies: - camelcase "^5.3.1" + camelcase "^6.0.0" cssesc "^3.0.0" icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.27" + loader-utils "^2.0.0" + postcss "^7.0.32" postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" + postcss-modules-local-by-default "^3.0.3" postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.3" - schema-utils "^2.6.6" - semver "^6.3.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" css-prefers-color-scheme@^3.1.1: version "3.1.1" @@ -4418,7 +4424,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -5230,6 +5236,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klona@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + known-css-properties@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" @@ -5498,7 +5509,7 @@ loud-rejection@*, loud-rejection@^2.2.0: currently-unhandled "^0.4.1" signal-exit "^3.0.2" -lru-cache@6.0.0: +lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== @@ -5865,7 +5876,7 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mkdirp@^1.0.3: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -5957,7 +5968,7 @@ neo-async@2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== -neo-async@^2.5.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -5967,22 +5978,21 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -next@^9.5.3: - version "9.5.3" - resolved "https://registry.yarnpkg.com/next/-/next-9.5.3.tgz#7af5270631f98d330a7f75a6e8e1ac202aa155e2" - integrity sha512-DGrpTNGV2RNMwLaSzpgbkbaUuVk30X71/roXHS10isSXo2Gm+qWcjonDyOxf1KmOvHZRHA/Fa+LaAR7ysdYS3A== +next@^9.5.4: + version "9.5.4" + resolved "https://registry.yarnpkg.com/next/-/next-9.5.4.tgz#3c6aa3fd38ff1711e956ea2b6833475e0262ec35" + integrity sha512-dicsJSxiUFcRjeZ/rNMAO3HS5ttFFuRHhdAn5g7lHnWUZ3MnEX4ggBIihaoUr6qu2So9KoqUPXpS91MuSXUmBw== dependencies: "@ampproject/toolbox-optimizer" "2.6.0" "@babel/code-frame" "7.10.4" "@babel/core" "7.7.7" "@babel/plugin-proposal-class-properties" "7.10.4" "@babel/plugin-proposal-export-namespace-from" "7.10.4" - "@babel/plugin-proposal-nullish-coalescing-operator" "7.10.4" "@babel/plugin-proposal-numeric-separator" "7.10.4" "@babel/plugin-proposal-object-rest-spread" "7.11.0" - "@babel/plugin-proposal-optional-chaining" "7.11.0" "@babel/plugin-syntax-bigint" "7.8.3" "@babel/plugin-syntax-dynamic-import" "7.8.3" + "@babel/plugin-syntax-jsx" "7.10.4" "@babel/plugin-transform-modules-commonjs" "7.10.4" "@babel/plugin-transform-runtime" "7.11.5" "@babel/preset-env" "7.11.5" @@ -5991,19 +6001,20 @@ next@^9.5.3: "@babel/preset-typescript" "7.10.4" "@babel/runtime" "7.11.2" "@babel/types" "7.11.5" - "@next/react-dev-overlay" "9.5.3" - "@next/react-refresh-utils" "9.5.3" + "@next/env" "9.5.4" + "@next/polyfill-module" "9.5.4" + "@next/react-dev-overlay" "9.5.4" + "@next/react-refresh-utils" "9.5.4" ast-types "0.13.2" - babel-plugin-syntax-jsx "6.18.0" babel-plugin-transform-define "2.0.0" babel-plugin-transform-react-remove-prop-types "0.4.24" browserslist "4.13.0" buffer "5.6.0" - cacache "13.0.1" + cacache "15.0.5" caniuse-lite "^1.0.30001113" chokidar "2.1.8" crypto-browserify "3.12.0" - css-loader "3.5.3" + css-loader "4.3.0" cssnano-simple "1.2.0" find-cache-dir "3.3.1" jest-worker "24.9.0" @@ -6020,15 +6031,15 @@ next@^9.5.3: react-is "16.13.1" react-refresh "0.8.3" resolve-url-loader "3.1.1" - sass-loader "8.0.2" - schema-utils "2.6.6" + sass-loader "10.0.2" + schema-utils "2.7.1" stream-browserify "3.0.0" style-loader "1.2.1" styled-jsx "3.3.0" use-subscription "1.4.1" vm-browserify "1.1.2" watchpack "2.0.0-beta.13" - web-vitals "0.2.1" + web-vitals "0.2.4" webpack "4.44.1" webpack-sources "1.4.3" @@ -6401,13 +6412,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -6837,7 +6841,7 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" -postcss-modules-local-by-default@^3.0.2: +postcss-modules-local-by-default@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== @@ -7022,7 +7026,7 @@ postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -7054,7 +7058,7 @@ postcss@7.0.32: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.34" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== @@ -7701,7 +7705,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7793,16 +7797,16 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== +sass-loader@10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.2.tgz#c7b73010848b264792dd45372eea0b87cba4401e" + integrity sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg== dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" + klona "^2.0.3" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^2.7.1" + semver "^7.3.2" sax@^1.2.4, sax@~1.2.4: version "1.2.4" @@ -7825,7 +7829,7 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6: +schema-utils@*, schema-utils@2.7.1, schema-utils@^2.6.6, schema-utils@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -7834,14 +7838,6 @@ schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@2.6.6: - version "2.6.6" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c" - integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA== - dependencies: - ajv "^6.12.0" - ajv-keywords "^3.4.1" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -7871,7 +7867,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.2: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -7926,13 +7922,6 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - shallow-equal@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" @@ -8155,12 +8144,11 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" -ssri@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" - integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g== +ssri@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" + integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== dependencies: - figgy-pudding "^3.5.1" minipass "^3.1.1" stable@^0.1.8: @@ -8619,7 +8607,7 @@ tar@^4.4.2: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@^6.0.5: +tar@^6.0.2, tar@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== @@ -9215,10 +9203,10 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" -web-vitals@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.1.tgz#60782fa690243fe35613759a0c26431f57ba7b2d" - integrity sha512-2pdRlp6gJpOCg0oMMqwFF0axjk5D9WInc09RSYtqFgPXQ15+YKNQ7YnBBEqAL5jvmfH9WvoXDMb8DHwux7pIew== +web-vitals@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511" + integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg== webidl-conversions@^4.0.2: version "4.0.2" From e64a555652bc88361f8549a9e7be3bae44061298 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 8 Oct 2020 15:02:48 -0700 Subject: [PATCH 33/71] Renamed methods. Initial work on realtime dashboard. --- components/common/RefreshButton.js | 2 +- components/layout/Header.js | 3 + components/metrics/MetricsBar.js | 2 +- components/metrics/MetricsTable.js | 2 +- components/metrics/RealtimeChart.js | 80 +++++++++++++++++++ components/pages/RealtimeDashboard.js | 36 +++++++++ components/pages/RealtimeDashboard.module.css | 3 + components/pages/TestConsole.js | 2 +- ...Test.module.css => TestConsole.module.css} | 0 lib/queries.js | 4 +- pages/api/realtime.js | 20 +++++ pages/api/website/[id]/metrics.js | 58 +++++++++++--- pages/api/website/[id]/pageviews.js | 6 +- pages/api/website/[id]/rankings.js | 68 ---------------- pages/api/website/[id]/stats.js | 28 +++++++ pages/realtime.js | 18 +++++ 16 files changed, 246 insertions(+), 86 deletions(-) create mode 100644 components/metrics/RealtimeChart.js create mode 100644 components/pages/RealtimeDashboard.js create mode 100644 components/pages/RealtimeDashboard.module.css rename components/pages/{Test.module.css => TestConsole.module.css} (100%) create mode 100644 pages/api/realtime.js delete mode 100644 pages/api/website/[id]/rankings.js create mode 100644 pages/api/website/[id]/stats.js create mode 100644 pages/realtime.js diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js index af754a9c..013cbf54 100644 --- a/components/common/RefreshButton.js +++ b/components/common/RefreshButton.js @@ -12,7 +12,7 @@ export default function RefreshButton({ websiteId }) { const dispatch = useDispatch(); const [dateRange] = useDateRange(websiteId); const [loading, setLoading] = useState(false); - const completed = useSelector(state => state.queries[`/api/website/${websiteId}/metrics`]); + const completed = useSelector(state => state.queries[`/api/website/${websiteId}/stats`]); function handleClick() { if (dateRange) { diff --git a/components/layout/Header.js b/components/layout/Header.js index c48fdd11..cc8baae3 100644 --- a/components/layout/Header.js +++ b/components/layout/Header.js @@ -30,6 +30,9 @@ export default function Header() { + + + diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index b7a47a10..ed2c7b12 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -19,7 +19,7 @@ export default function MetricsBar({ websiteId, token, className }) { } = usePageQuery(); const { data, error, loading } = useFetch( - `/api/website/${websiteId}/metrics`, + `/api/website/${websiteId}/stats`, { start_at: +startDate, end_at: +endDate, diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 6850a3bf..162dce0f 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -38,7 +38,7 @@ export default function MetricsTable({ } = usePageQuery(); const { data, loading, error } = useFetch( - `/api/website/${websiteId}/rankings`, + `/api/website/${websiteId}/metrics`, { type, start_at: +startDate, diff --git a/components/metrics/RealtimeChart.js b/components/metrics/RealtimeChart.js new file mode 100644 index 00000000..0eb3c528 --- /dev/null +++ b/components/metrics/RealtimeChart.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import tinycolor from 'tinycolor2'; +import BarChart from './BarChart'; +import useTheme from 'hooks/useTheme'; +import { THEME_COLORS } from 'lib/constants'; + +export default function RealtimeChart({ websiteId, data, unit, records, className, loading }) { + const intl = useIntl(); + const [theme] = useTheme(); + const primaryColor = tinycolor(THEME_COLORS[theme].primary); + const colors = { + views: { + background: primaryColor.setAlpha(0.4).toRgbString(), + border: primaryColor.setAlpha(0.5).toRgbString(), + }, + visitors: { + background: primaryColor.setAlpha(0.6).toRgbString(), + border: primaryColor.setAlpha(0.7).toRgbString(), + }, + }; + + const handleUpdate = chart => { + const { + data: { datasets }, + } = chart; + + datasets[0].data = data.uniques; + datasets[0].label = intl.formatMessage({ + id: 'metrics.unique-visitors', + defaultMessage: 'Unique visitors', + }); + datasets[1].data = data.pageviews; + datasets[1].label = intl.formatMessage({ + id: 'metrics.page-views', + defaultMessage: 'Page views', + }); + + chart.update(); + }; + + if (!data) { + return null; + } + + return ( + + ); +} diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js new file mode 100644 index 00000000..7fb56596 --- /dev/null +++ b/components/pages/RealtimeDashboard.js @@ -0,0 +1,36 @@ +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'; + +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 = [{ label: 'All websites', value: 0 }].concat( + data.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)); + } + + return ( + + +
Real time
+ +
+ +
+ ); +} diff --git a/components/pages/RealtimeDashboard.module.css b/components/pages/RealtimeDashboard.module.css new file mode 100644 index 00000000..abde53a3 --- /dev/null +++ b/components/pages/RealtimeDashboard.module.css @@ -0,0 +1,3 @@ +.container { + display: flex; +} diff --git a/components/pages/TestConsole.js b/components/pages/TestConsole.js index f6fa8a23..fef6c620 100644 --- a/components/pages/TestConsole.js +++ b/components/pages/TestConsole.js @@ -7,7 +7,7 @@ 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 styles from './TestConsole.module.css'; import WebsiteChart from '../metrics/WebsiteChart'; import EventsChart from '../metrics/EventsChart'; import Button from '../common/Button'; diff --git a/components/pages/Test.module.css b/components/pages/TestConsole.module.css similarity index 100% rename from components/pages/Test.module.css rename to components/pages/TestConsole.module.css diff --git a/lib/queries.js b/lib/queries.js index 3b138faa..6312057f 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -285,7 +285,7 @@ export async function createAccount(data) { ); } -export function getMetrics(website_id, start_at, end_at, filters = {}) { +export function getWebsiteStats(website_id, start_at, end_at, filters = {}) { const params = [website_id, start_at, end_at]; const { url } = filters; let urlFilter = ''; @@ -317,7 +317,7 @@ export function getMetrics(website_id, start_at, end_at, filters = {}) { ); } -export function getPageviews( +export function getPageviewStats( website_id, start_at, end_at, diff --git a/pages/api/realtime.js b/pages/api/realtime.js new file mode 100644 index 00000000..7717c9b9 --- /dev/null +++ b/pages/api/realtime.js @@ -0,0 +1,20 @@ +import { useAuth } from 'lib/middleware'; +import { ok, unauthorized, methodNotAllowed } from 'lib/response'; + +export default async (req, res) => { + await useAuth(req, res); + + const { is_admin } = req.auth; + + if (!is_admin) { + return unauthorized(res); + } + + if (req.method === 'GET') { + const [pageviews, sessions, events] = await Promise.all([[], [], []]); + + return ok(res, { pageviews, sessions, events }); + } + + return methodNotAllowed(res); +}; diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js index 91a0f4cf..17aa6daa 100644 --- a/pages/api/website/[id]/metrics.js +++ b/pages/api/website/[id]/metrics.js @@ -1,27 +1,67 @@ -import { getMetrics } from 'lib/queries'; -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { getPageviewMetrics, getSessionMetrics } from 'lib/queries'; +import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; +import { DOMAIN_REGEX } from 'lib/constants'; import { allowQuery } from 'lib/auth'; +const sessionColumns = ['browser', 'os', 'device', 'country']; +const pageviewColumns = ['url', 'referrer']; + +function getTable(type) { + if (type === 'event') { + return 'event'; + } + + if (sessionColumns.includes(type)) { + return 'session'; + } + + return 'pageview'; +} + +function getColumn(type) { + if (type === 'event') { + return `concat(event_type, ':', event_value)`; + } + return type; +} + export default async (req, res) => { if (req.method === 'GET') { if (!(await allowQuery(req))) { return unauthorized(res); } - const { id, start_at, end_at, url } = req.query; + const { id, type, start_at, end_at, domain, url } = req.query; + + if (domain && !DOMAIN_REGEX.test(domain)) { + return badRequest(res); + } const websiteId = +id; const startDate = new Date(+start_at); const endDate = new Date(+end_at); - const metrics = await getMetrics(websiteId, startDate, endDate, { url }); + if (sessionColumns.includes(type)) { + const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url }); - const stats = Object.keys(metrics[0]).reduce((obj, key) => { - obj[key] = Number(metrics[0][key]) || 0; - return obj; - }, {}); + return ok(res, data); + } - return ok(res, stats); + if (type === 'event' || pageviewColumns.includes(type)) { + const data = await getPageviewMetrics( + websiteId, + startDate, + endDate, + getColumn(type), + getTable(type), + { + domain, + url: type !== 'url' && url, + }, + ); + + return ok(res, data); + } } return methodNotAllowed(res); diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js index 2191a4c4..07362359 100644 --- a/pages/api/website/[id]/pageviews.js +++ b/pages/api/website/[id]/pageviews.js @@ -1,5 +1,5 @@ import moment from 'moment-timezone'; -import { getPageviews } from 'lib/queries'; +import { getPageviewStats } from 'lib/queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; @@ -22,8 +22,8 @@ export default async (req, res) => { } const [pageviews, uniques] = await Promise.all([ - getPageviews(websiteId, startDate, endDate, tz, unit, '*', url), - getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url), + getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', url), + getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', url), ]); return ok(res, { pageviews, uniques }); diff --git a/pages/api/website/[id]/rankings.js b/pages/api/website/[id]/rankings.js deleted file mode 100644 index 17aa6daa..00000000 --- a/pages/api/website/[id]/rankings.js +++ /dev/null @@ -1,68 +0,0 @@ -import { getPageviewMetrics, getSessionMetrics } from 'lib/queries'; -import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; -import { DOMAIN_REGEX } from 'lib/constants'; -import { allowQuery } from 'lib/auth'; - -const sessionColumns = ['browser', 'os', 'device', 'country']; -const pageviewColumns = ['url', 'referrer']; - -function getTable(type) { - if (type === 'event') { - return 'event'; - } - - if (sessionColumns.includes(type)) { - return 'session'; - } - - return 'pageview'; -} - -function getColumn(type) { - if (type === 'event') { - return `concat(event_type, ':', event_value)`; - } - return type; -} - -export default async (req, res) => { - if (req.method === 'GET') { - if (!(await allowQuery(req))) { - return unauthorized(res); - } - - const { id, type, start_at, end_at, domain, url } = req.query; - - if (domain && !DOMAIN_REGEX.test(domain)) { - return badRequest(res); - } - - const websiteId = +id; - const startDate = new Date(+start_at); - const endDate = new Date(+end_at); - - if (sessionColumns.includes(type)) { - const data = await getSessionMetrics(websiteId, startDate, endDate, type, { url }); - - return ok(res, data); - } - - if (type === 'event' || pageviewColumns.includes(type)) { - const data = await getPageviewMetrics( - websiteId, - startDate, - endDate, - getColumn(type), - getTable(type), - { - domain, - url: type !== 'url' && url, - }, - ); - - return ok(res, data); - } - } - - return methodNotAllowed(res); -}; diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js new file mode 100644 index 00000000..8b80a363 --- /dev/null +++ b/pages/api/website/[id]/stats.js @@ -0,0 +1,28 @@ +import { getWebsiteStats } from 'lib/queries'; +import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { allowQuery } from 'lib/auth'; + +export default async (req, res) => { + if (req.method === 'GET') { + if (!(await allowQuery(req))) { + return unauthorized(res); + } + + const { id, start_at, end_at, url } = req.query; + + const websiteId = +id; + const startDate = new Date(+start_at); + const endDate = new Date(+end_at); + + const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url }); + + const stats = Object.keys(metrics[0]).reduce((obj, key) => { + obj[key] = Number(metrics[0][key]) || 0; + return obj; + }, {}); + + return ok(res, stats); + } + + return methodNotAllowed(res); +}; diff --git a/pages/realtime.js b/pages/realtime.js new file mode 100644 index 00000000..9f1ebffa --- /dev/null +++ b/pages/realtime.js @@ -0,0 +1,18 @@ +import React from 'react'; +import Layout from 'components/layout/Layout'; +import RealtimeDashboard from 'components/pages/RealtimeDashboard'; +import useRequireLogin from 'hooks/useRequireLogin'; + +export default function RealtimePage() { + const { loading } = useRequireLogin(); + + if (loading) { + return null; + } + + return ( + + + + ); +} From fdc92d087b38f4e8f118dc4d41aa69947181cb6e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 8 Oct 2020 23:26:05 -0700 Subject: [PATCH 34/71] Update realtime chart. --- components/metrics/ActiveUsers.js | 2 +- components/metrics/BarChart.js | 2 + components/metrics/PageviewsChart.js | 4 +- components/metrics/WebsiteChart.js | 14 ++-- components/pages/RealtimeDashboard.js | 99 ++++++++++++++++++++++----- hooks/useFetch.js | 8 +-- lang/da-DK.json | 2 + lang/de-DE.json | 2 + lang/el-GR.json | 2 + lang/en-US.json | 2 + lang/es-MX.json | 2 + lang/fo-FO.json | 2 + lang/fr-FR.json | 2 + lang/id-ID.json | 2 + lang/ja-JP.json | 2 + lang/mn-MN.json | 2 + lang/nb-NO.json | 2 + lang/nl-NL.json | 2 + lang/pt-PT.json | 2 + lang/ro-RO.json | 2 + lang/ru-RU.json | 2 + lang/sv-SE.json | 2 + lang/tr-TR.json | 2 + lang/uk-UA.json | 2 + lang/zh-CN.json | 2 + lib/date.js | 3 + lib/queries.js | 63 ++++++++++++++--- lib/web.js | 12 ++-- package.json | 2 +- pages/api/realtime.js | 43 ++++++++++-- pages/api/website/[id]/events.js | 4 +- pages/api/website/[id]/pageviews.js | 4 +- 32 files changed, 240 insertions(+), 58 deletions(-) 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); From 9737127bb12cae34f39e926e7044f82d65918a88 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 01:04:06 -0700 Subject: [PATCH 35/71] Updated polling logic. --- components/metrics/PageviewsChart.js | 12 ++++++++++-- components/pages/RealtimeDashboard.js | 12 ++++++++---- hooks/useFetch.js | 14 ++++++-------- lib/url.js | 4 ++++ lib/web.js | 6 +++--- pages/api/realtime.js | 4 ++-- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js index d20db5eb..b937abe6 100644 --- a/components/metrics/PageviewsChart.js +++ b/components/metrics/PageviewsChart.js @@ -6,7 +6,15 @@ import BarChart from './BarChart'; import useTheme from 'hooks/useTheme'; import { THEME_COLORS } from 'lib/constants'; -export default function PageviewsChart({ websiteId, data, unit, records, className, loading }) { +export default function PageviewsChart({ + websiteId, + data, + unit, + records, + className, + loading, + animationDuration = 300, +}) { const intl = useIntl(); const [theme] = useTheme(); const primaryColor = tinycolor(THEME_COLORS[theme].primary); @@ -76,7 +84,7 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa ]} unit={unit} records={records} - animationDuration={visible ? 300 : 0} + animationDuration={visible ? animationDuration : 0} onUpdate={handleUpdate} loading={loading} /> diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 35884353..ebc2db6e 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -35,11 +35,16 @@ function mapData(data) { export default function RealtimeDashboard() { const [data, setData] = useState(); const [website, setWebsite] = useState(); + const [lastTime, setLastTime] = useState(); 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 } }, + { type: 'update', start_at: lastTime }, + { + disabled: !init?.token, + interval: 5000, + headers: { 'x-umami-token': init?.token }, + }, ); const chartData = useMemo(() => { @@ -48,8 +53,6 @@ export default function RealtimeDashboard() { 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), @@ -70,6 +73,7 @@ export default function RealtimeDashboard() { events: filterTime(state.events, time).concat(events), })); } + setLastTime(Date.now()); }, [updates, init]); if (!init || loading || !data) { diff --git a/hooks/useFetch.js b/hooks/useFetch.js index b74a8fe7..e41b4dbc 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -11,10 +11,7 @@ export default function useFetch(url, params = {}, options = {}) { const [error, setError] = useState(); const [loading, setLoadiing] = useState(false); const { basePath } = useRouter(); - const keys = Object.keys(params) - .sort() - .map(key => params[key]); - const { update = [], onDataLoad = () => {}, disabled, headers } = options; + const { update = [], onDataLoad = () => {}, disabled, headers, interval, delay = 0 } = options; async function loadData() { try { @@ -43,10 +40,11 @@ export default function useFetch(url, params = {}, options = {}) { } useEffect(() => { + console.log('effect', params); if (url && !disabled) { - const { interval, delay = 0 } = options; - - setTimeout(() => loadData(), delay); + if (!data) { + setTimeout(() => loadData(), delay); + } const id = interval ? setInterval(() => loadData(), interval) : null; @@ -54,7 +52,7 @@ export default function useFetch(url, params = {}, options = {}) { clearInterval(id); }; } - }, [url, disabled, ...keys, ...update]); + }, [data, url, disabled, ...update]); return { data, status, error, loading }; } diff --git a/lib/url.js b/lib/url.js index 500736f9..deec7fe1 100644 --- a/lib/url.js +++ b/lib/url.js @@ -28,3 +28,7 @@ export function getQueryString(params = {}) { return ''; } + +export function makeUrl(url, params) { + return `${url}${getQueryString(params)}`; +} diff --git a/lib/web.js b/lib/web.js index 24d1d982..13889cbe 100644 --- a/lib/web.js +++ b/lib/web.js @@ -1,4 +1,4 @@ -import { getQueryString } from './url'; +import { makeUrl } from './url'; export const apiRequest = (method, url, body, headers) => fetch(url, { @@ -20,10 +20,10 @@ export const apiRequest = (method, url, body, headers) => }); export const get = (url, params, headers) => - apiRequest('get', `${url}${getQueryString(params)}`, undefined, headers); + apiRequest('get', makeUrl(url, params), undefined, headers); export const del = (url, params, headers) => - apiRequest('delete', `${url}${getQueryString(params)}`, undefined, headers); + apiRequest('delete', makeUrl(url, params), undefined, headers); export const post = (url, params, headers) => apiRequest('post', url, JSON.stringify(params), headers); diff --git a/pages/api/realtime.js b/pages/api/realtime.js index 2130521b..64cbea5e 100644 --- a/pages/api/realtime.js +++ b/pages/api/realtime.js @@ -16,7 +16,7 @@ export default async (req, res) => { } if (req.method === 'GET') { - const { type } = req.query; + const { type, start_at } = req.query; const { user_id } = req.auth; if (type === 'init') { @@ -37,7 +37,7 @@ export default async (req, res) => { const { websites } = await parseToken(token); - const [pageviews, sessions, events] = await getData(websites, new Date()); + const [pageviews, sessions, events] = await getData(websites, new Date(+start_at)); return ok(res, { pageviews, sessions, events }); } From db9b2385852f26bc543dad8a25d079d65e368668 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 01:20:02 -0700 Subject: [PATCH 36/71] Updated merge logic. --- components/pages/RealtimeDashboard.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index ebc2db6e..341b9e13 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -9,7 +9,7 @@ import PageviewsChart from '../metrics/PageviewsChart'; import { getDateArray } from '../../lib/date'; function filterTime(data, time) { - return data.filter(({ created_at }) => new Date(created_at).getTime() > time); + return data.filter(({ created_at }) => new Date(created_at).getTime() >= time); } function mapData(data) { @@ -66,11 +66,11 @@ export default function RealtimeDashboard() { setData(init.data); } else if (updates) { const { pageviews, sessions, events } = updates; - const time = subMinutes(startOfMinute(new Date()), 30).getTime(); + const minTime = 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), + pageviews: filterTime(state.pageviews, minTime).concat(filterTime(pageviews, lastTime)), + sessions: filterTime(state.sessions, minTime).concat(filterTime(sessions, lastTime)), + events: filterTime(state.events, minTime).concat(filterTime(events, lastTime)), })); } setLastTime(Date.now()); From b682e41afff4cfeb70d3d121c900790ef77a8495 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 02:56:15 -0700 Subject: [PATCH 37/71] RealtimeLog component. --- components/common/Table.js | 4 +- components/common/Table.module.css | 1 + components/metrics/RealtimeChart.js | 114 ++++++++-------------- components/metrics/RealtimeLog.js | 95 ++++++++++++++++++ components/metrics/RealtimeLog.module.css | 8 ++ components/pages/RealtimeDashboard.js | 64 ++++-------- 6 files changed, 163 insertions(+), 123 deletions(-) create mode 100644 components/metrics/RealtimeLog.js create mode 100644 components/metrics/RealtimeLog.module.css diff --git a/components/common/Table.js b/components/common/Table.js index a8a42b78..61eb4f6e 100644 --- a/components/common/Table.js +++ b/components/common/Table.js @@ -3,13 +3,13 @@ import classNames from 'classnames'; import NoData from 'components/common/NoData'; import styles from './Table.module.css'; -export default function Table({ columns, rows, empty }) { +export default function Table({ className, columns, rows, empty }) { if (empty && rows.length === 0) { return empty; } return ( -
+
{columns.map(({ key, label, className, style, header }) => (
{ - const { - data: { datasets }, - } = chart; + 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; + }, {}); - datasets[0].data = data.uniques; - datasets[0].label = intl.formatMessage({ - id: 'metrics.unique-visitors', - defaultMessage: 'Unique visitors', - }); - datasets[1].data = data.pageviews; - datasets[1].label = intl.formatMessage({ - id: 'metrics.page-views', - defaultMessage: 'Page views', - }); - - chart.update(); - }; - - if (!data) { - return null; - } - - return ( - - ); + return arr; +} + +export default function RealtimeChart({ data, ...props }) { + const chartData = useMemo(() => { + if (data) { + const endDate = startOfMinute(new Date()); + const startDate = subMinutes(endDate, 30); + const unit = 'minute'; + + return { + pageviews: getDateArray(mapData(data.pageviews), startDate, endDate, unit), + sessions: getDateArray(mapData(data.sessions), startDate, endDate, unit), + }; + } + return { pageviews: [], sessions: [] }; + }, [data]); + + return ; } diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js new file mode 100644 index 00000000..b3a82050 --- /dev/null +++ b/components/metrics/RealtimeLog.js @@ -0,0 +1,95 @@ +import React, { useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; +import firstBy from 'thenby'; +import { format } from 'date-fns'; +import Table from 'components/common/Table'; +import styles from './RealtimeLog.module.css'; +import useLocale from '../../hooks/useLocale'; +import useCountryNames from '../../hooks/useCountryNames'; +import { BROWSERS } from '../../lib/constants'; + +export default function RealtimeLog({ data, websites }) { + const [locale] = useLocale(); + const countryNames = useCountryNames(locale); + const logs = useMemo(() => { + const { pageviews, sessions, events } = data; + return [...pageviews, ...sessions, ...events].sort(firstBy('created_at', -1)); + }, [data]); + + const columns = [ + { + key: 'time', + label: , + className: 'col', + render: ({ created_at }) => format(new Date(created_at), 'H:mm:ss'), + }, + { + key: 'website', + label: , + className: 'col', + render: getWebsite, + }, + { + key: 'type', + label: , + className: 'col', + render: getType, + }, + { + key: 'type', + className: 'col', + render: getDescription, + }, + ]; + + function getType({ view_id, session_id, event_id }) { + if (event_id) { + return ; + } + if (view_id) { + return ; + } + if (session_id) { + return ; + } + return null; + } + + function getWebsite({ website_id }) { + return websites.find(n => n.website_id === website_id)?.name; + } + + function getDescription({ + event_type, + event_value, + view_id, + session_id, + url, + browser, + os, + country, + device, + }) { + if (event_type) { + return `${event_type}:${event_value}`; + } + if (view_id) { + return url; + } + if (session_id) { + return ( + + ); + } + } + + return ( +
+ + + ); +} diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css new file mode 100644 index 00000000..66a2efd3 --- /dev/null +++ b/components/metrics/RealtimeLog.module.css @@ -0,0 +1,8 @@ +.table { + font-size: var(--font-size-small); +} + +.row { + display: flex; + border-bottom: 1px solid var(--gray300); +} diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 341b9e13..348b6642 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -1,37 +1,20 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; -import { subMinutes, startOfMinute, parseISO, format } from 'date-fns'; +import { subMinutes, startOfMinute } 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'; +import RealtimeChart from '../metrics/RealtimeChart'; +import RealtimeLog from '../metrics/RealtimeLog'; + +const REALTIME_RANGE = 30; +const REALTIME_INTERVAL = 5000; 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(); @@ -42,39 +25,25 @@ export default function RealtimeDashboard() { { type: 'update', start_at: lastTime }, { disabled: !init?.token, - interval: 5000, + interval: REALTIME_INTERVAL, headers: { 'x-umami-token': init?.token }, }, ); - const chartData = useMemo(() => { - if (data) { - const endDate = startOfMinute(new Date()); - const startDate = subMinutes(endDate, 30); - const unit = 'minute'; - - 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 minTime = subMinutes(startOfMinute(new Date()), 30).getTime(); + const minTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); setData(state => ({ - pageviews: filterTime(state.pageviews, minTime).concat(filterTime(pageviews, lastTime)), - sessions: filterTime(state.sessions, minTime).concat(filterTime(sessions, lastTime)), - events: filterTime(state.events, minTime).concat(filterTime(events, lastTime)), + pageviews: filterTime(state.pageviews.concat(pageviews), minTime), + sessions: filterTime(state.sessions.concat(sessions), minTime), + events: filterTime(state.events.concat(events), minTime), })); } setLastTime(Date.now()); - }, [updates, init]); + }, [init, updates]); if (!init || loading || !data) { return null; @@ -99,7 +68,12 @@ export default function RealtimeDashboard() { - + +
+
+ +
+
); } From 8e0ea48c871c02900119bd8fce0744c6404bceb9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 04:21:59 -0700 Subject: [PATCH 38/71] Updated log table rendering. --- assets/bolt.svg | 1 + assets/eye.svg | 1 + assets/visitor.svg | 1 + components/common/Table.js | 45 ++++++++----- components/metrics/RealtimeLog.js | 77 ++++++++++++++++------- components/metrics/RealtimeLog.module.css | 18 ++++++ hooks/useFetch.js | 1 - lang/da-DK.json | 6 ++ lang/de-DE.json | 6 ++ lang/el-GR.json | 6 ++ lang/en-US.json | 6 ++ lang/es-MX.json | 6 ++ lang/fo-FO.json | 6 ++ lang/fr-FR.json | 6 ++ lang/id-ID.json | 6 ++ lang/ja-JP.json | 6 ++ lang/mn-MN.json | 6 ++ lang/nb-NO.json | 6 ++ lang/nl-NL.json | 6 ++ lang/pt-PT.json | 6 ++ lang/ro-RO.json | 6 ++ lang/ru-RU.json | 6 ++ lang/sv-SE.json | 6 ++ lang/tr-TR.json | 6 ++ lang/uk-UA.json | 6 ++ lang/zh-CN.json | 6 ++ 26 files changed, 221 insertions(+), 37 deletions(-) create mode 100644 assets/bolt.svg create mode 100644 assets/eye.svg create mode 100644 assets/visitor.svg diff --git a/assets/bolt.svg b/assets/bolt.svg new file mode 100644 index 00000000..4654a1eb --- /dev/null +++ b/assets/bolt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/eye.svg b/assets/eye.svg new file mode 100644 index 00000000..09c93453 --- /dev/null +++ b/assets/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/visitor.svg b/assets/visitor.svg new file mode 100644 index 00000000..591873a5 --- /dev/null +++ b/assets/visitor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/common/Table.js b/components/common/Table.js index 61eb4f6e..d0a1e0c7 100644 --- a/components/common/Table.js +++ b/components/common/Table.js @@ -3,7 +3,15 @@ import classNames from 'classnames'; import NoData from 'components/common/NoData'; import styles from './Table.module.css'; -export default function Table({ className, columns, rows, empty }) { +export default function Table({ + columns, + rows, + empty, + className, + bodyClassName, + rowKey, + children, +}) { if (empty && rows.length === 0) { return empty; } @@ -21,22 +29,29 @@ export default function Table({ className, columns, rows, empty }) { ))} -
+
{rows.length === 0 && } - {rows.map((row, rowIndex) => ( -
- {columns.map(({ key, render, className, style, cell }) => ( -
- {render ? render(row) : row[key]} -
- ))} -
- ))} + {!children && + rows.map((row, index) => { + const id = rowKey ? rowKey(row) : index; + return ; + })} + {children}
); } + +export const TableRow = ({ columns, row }) => ( +
+ {columns.map(({ key, render, className, style, cell }, index) => ( +
+ {render ? render(row) : row[key]} +
+ ))} +
+); diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index b3a82050..fce9f7e9 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -1,14 +1,20 @@ import React, { useMemo } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { FixedSizeList } from 'react-window'; import firstBy from 'thenby'; import { format } from 'date-fns'; -import Table from 'components/common/Table'; +import Icon from 'components/common/Icon'; +import Table, { TableRow } from 'components/common/Table'; +import useLocale from 'hooks/useLocale'; +import useCountryNames from 'hooks/useCountryNames'; +import { BROWSERS } from 'lib/constants'; +import Bolt from 'assets/bolt.svg'; +import Visitor from 'assets/visitor.svg'; +import Eye from 'assets/eye.svg'; import styles from './RealtimeLog.module.css'; -import useLocale from '../../hooks/useLocale'; -import useCountryNames from '../../hooks/useCountryNames'; -import { BROWSERS } from '../../lib/constants'; export default function RealtimeLog({ data, websites }) { + const intl = useIntl(); const [locale] = useLocale(); const countryNames = useCountryNames(locale); const logs = useMemo(() => { @@ -19,38 +25,51 @@ export default function RealtimeLog({ data, websites }) { const columns = [ { key: 'time', - label: , - className: 'col', + label: , + className: 'col-1', render: ({ created_at }) => format(new Date(created_at), 'H:mm:ss'), }, { key: 'website', label: , - className: 'col', + className: 'col-2', render: getWebsite, }, { key: 'type', - label: , - className: 'col', - render: getType, - }, - { - key: 'type', - className: 'col', - render: getDescription, + label: , + className: 'col-9', + render: row => ( + <> + + {getDescription(row)} + + ), }, ]; function getType({ view_id, session_id, event_id }) { if (event_id) { - return ; + return intl.formatMessage({ id: 'label.event', defaultMessage: 'Event' }); } if (view_id) { - return ; + return intl.formatMessage({ id: 'label.pageview', defaultMessage: 'Pageview' }); } if (session_id) { - return ; + return intl.formatMessage({ id: 'label.visitor', defaultMessage: 'Visitor' }); + } + return null; + } + + function getIcon({ view_id, session_id, event_id }) { + if (event_id) { + return ; + } + if (view_id) { + return ; + } + if (session_id) { + return ; } return null; } @@ -71,7 +90,11 @@ export default function RealtimeLog({ data, websites }) { device, }) { if (event_type) { - return `${event_type}:${event_value}`; + return ( +
+ {event_type} {event_value} +
+ ); } if (view_id) { return url; @@ -87,9 +110,21 @@ export default function RealtimeLog({ data, websites }) { } } + const Row = ({ index, style }) => { + return ( +
+ +
+ ); + }; + return (
-
+
+ + {Row} + +
); } diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index 66a2efd3..a2ffa893 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -6,3 +6,21 @@ display: flex; border-bottom: 1px solid var(--gray300); } + +.body { + height: 600px; + overflow: auto; +} + +.event { + font-size: var(--font-size-small); + padding: 2px 4px; + border: 1px solid var(--gray300); + border-radius: 4px; + margin-right: 10px; +} + +.icon { + align-self: center; + margin-right: 20px; +} diff --git a/hooks/useFetch.js b/hooks/useFetch.js index e41b4dbc..5301e2a1 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -40,7 +40,6 @@ export default function useFetch(url, params = {}, options = {}) { } useEffect(() => { - console.log('effect', params); if (url && !disabled) { if (!data) { setTimeout(() => loadData(), delay); diff --git a/lang/da-DK.json b/lang/da-DK.json index ae8c4321..9d33fd7b 100644 --- a/lang/da-DK.json +++ b/lang/da-DK.json @@ -26,6 +26,7 @@ "label.default-date-range": "Default date range", "label.domain": "Domæne", "label.enable-share-url": "Aktivér delings-URL", + "label.event": "Event", "label.invalid": "Ugyldig", "label.invalid-domain": "Ugyldigt domæne", "label.last-days": "Sidste {x} dage", @@ -34,6 +35,7 @@ "label.logout": "Log ud", "label.name": "Navn", "label.new-password": "Ny adgangskode", + "label.pageview": "Pageview", "label.password": "Adgangskode", "label.passwords-dont-match": "Adgangskoder matcher ikke", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Dette år", "label.timezone": "Timezone", "label.today": "Idag", + "label.type": "Type", "label.unknown": "Ukendt", "label.username": "Brugernavn", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Hjemmesider", "message.active-users": "{x} nuværende {x, plural, one {bruger} other {brugere}}", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Få sporingskode", "message.go-to-settings": "Gå til betjeningspanel", "message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/de-DE.json b/lang/de-DE.json index fdc7c3a1..7821f826 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -26,6 +26,7 @@ "label.default-date-range": "Voreingestellter Datumsbereich", "label.domain": "Domain", "label.enable-share-url": "Freigabe-URL aktivieren", + "label.event": "Event", "label.invalid": "Ungültig", "label.invalid-domain": "Ungültige Domain", "label.last-days": "Letzten {x} Tage", @@ -34,6 +35,7 @@ "label.logout": "Abmelden", "label.name": "Name", "label.new-password": "Neues Passwort", + "label.pageview": "Pageview", "label.password": "Passwort", "label.passwords-dont-match": "Passwörter stimmen nicht überein", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Dieses Jahr", "label.timezone": "Zeitzone", "label.today": "Heute", + "label.type": "Type", "label.unknown": "Unbekannt", "label.username": "Benutzername", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Webseiten", "message.active-users": "{x} {x, plural, one {aktiver Besucher} other {aktive Besucher}}", "message.confirm-delete": "Sind sie sich sicher {target} zu löschen?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Erstelle Tracking Kennung", "message.go-to-settings": "Zu den Einstellungen", "message.incorrect-username-password": "Falsches Passwort oder Benutzername.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Eine neue Version umami {version} ist verfügbar!", "message.no-data-available": "Keine Daten vorhanden.", "message.no-websites-configured": "Es ist keine Webseite vorhanden.", diff --git a/lang/el-GR.json b/lang/el-GR.json index ec4b3217..88d4a0e1 100644 --- a/lang/el-GR.json +++ b/lang/el-GR.json @@ -26,6 +26,7 @@ "label.default-date-range": "Προεπιλεγμένο εύρος ημερομηνιών", "label.domain": "Τομέας", "label.enable-share-url": "Ενεργοποίηση κοινής χρήσης URL", + "label.event": "Event", "label.invalid": "Μη έγκυρο", "label.invalid-domain": "Μη έγκυρος τομέας", "label.last-days": "Τελευταίες {x} ημέρες", @@ -34,6 +35,7 @@ "label.logout": "Αποσύνδεση", "label.name": "Όνομα", "label.new-password": "Νέος κωδικός", + "label.pageview": "Pageview", "label.password": "Κωδικός", "label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν", "label.profile": "Προφίλ", @@ -45,8 +47,11 @@ "label.this-year": "Αυτή την χρονιά", "label.timezone": "Ζώνη ώρας", "label.today": "Σήμερα", + "label.type": "Type", "label.unknown": "Άγνωστο", "label.username": "Όνομα χρήστη", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Ιστότοποι", "message.active-users": "{x} ενεργοί {x, plural, one {επισκέπτης} other {επισκέπτες}}", "message.confirm-delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {target};", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Λήψη κώδικα παρακολούθησης", "message.go-to-settings": "Μεταβείτε στις ρυθμίσεις", "message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "A new version of umami {version} is available!", "message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.", "message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.", diff --git a/lang/en-US.json b/lang/en-US.json index 7251c123..cadd0512 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -26,6 +26,7 @@ "label.default-date-range": "Default date range", "label.domain": "Domain", "label.enable-share-url": "Enable share URL", + "label.event": "Event", "label.invalid": "Invalid", "label.invalid-domain": "Invalid domain", "label.last-days": "Last {x} days", @@ -34,6 +35,7 @@ "label.logout": "Logout", "label.name": "Name", "label.new-password": "New password", + "label.pageview": "Pageview", "label.password": "Password", "label.passwords-dont-match": "Passwords don't match", "label.profile": "Profile", @@ -45,8 +47,11 @@ "label.this-year": "This year", "label.timezone": "Timezone", "label.today": "Today", + "label.type": "Type", "label.unknown": "Unknown", "label.username": "Username", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}", "message.confirm-delete": "Are your sure you want to delete {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Get tracking code", "message.go-to-settings": "Go to settings", "message.incorrect-username-password": "Incorrect username/password.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/es-MX.json b/lang/es-MX.json index 9e6a6206..48efe589 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -26,6 +26,7 @@ "label.default-date-range": "Default date range", "label.domain": "Dominio", "label.enable-share-url": "Habilitar compartir URL", + "label.event": "Event", "label.invalid": "Inválido", "label.invalid-domain": "Dominio inválido", "label.last-days": "Últimos {x} días", @@ -34,6 +35,7 @@ "label.logout": "Cerrar sesión", "label.name": "Nombre", "label.new-password": "Nueva contraseña", + "label.pageview": "Pageview", "label.password": "Contraseña", "label.passwords-dont-match": "Las contraseñas no coinciden", "label.profile": "Perfil", @@ -45,8 +47,11 @@ "label.this-year": "Este año", "label.timezone": "Timezone", "label.today": "Hoy", + "label.type": "Type", "label.unknown": "Unknown", "label.username": "Nombre de usuario", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Sitios", "message.active-users": "{x} {x, plural, one {activo} other {activos}}", "message.confirm-delete": "¿Estás seguro(a) de querer eliminar {target}?", @@ -57,6 +62,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/fo-FO.json b/lang/fo-FO.json index 2c379960..2969b83e 100644 --- a/lang/fo-FO.json +++ b/lang/fo-FO.json @@ -26,6 +26,7 @@ "label.default-date-range": "Standard dato", "label.domain": "Økisnavn", "label.enable-share-url": "Virkja deili leinki", + "label.event": "Event", "label.invalid": "Ógilda", "label.invalid-domain": "Ógilt økisnavn", "label.last-days": "Seinastu {x} dagarnar", @@ -34,6 +35,7 @@ "label.logout": "Rita út", "label.name": "Navn", "label.new-password": "Nýtt loyniorð", + "label.pageview": "Pageview", "label.password": "Loyniorð", "label.passwords-dont-match": "Loyniorðini eru ikki eins", "label.profile": "Brúkari", @@ -45,8 +47,11 @@ "label.this-year": "Hetta árið", "label.timezone": "Tíðarsona", "label.today": "Í dag", + "label.type": "Type", "label.unknown": "Ókent", "label.username": "Brúkaranavn", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Heimasíður", "message.active-users": "{x} í løtuni {x, plural, one {vitjandi} other { vitjandi }}", "message.confirm-delete": "Ert tú sikkur at tú ynskir at sletta {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Fá sporings kotu", "message.go-to-settings": "Far til stillingar", "message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index 013f89e5..b62ce587 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -26,6 +26,7 @@ "label.default-date-range": "Default date range", "label.domain": "Domaine", "label.enable-share-url": "Activer le partage d'URL", + "label.event": "Event", "label.invalid": "Invalide", "label.invalid-domain": "Domaine invalide", "label.last-days": "{x} derniers jours", @@ -34,6 +35,7 @@ "label.logout": "Déconnexion", "label.name": "Nom", "label.new-password": "Nouveau mot de passe", + "label.pageview": "Pageview", "label.password": "Mot de passe", "label.passwords-dont-match": "Les mots de passe ne correspondent pas", "label.profile": "Profile", @@ -45,8 +47,11 @@ "label.this-year": "Cette année", "label.timezone": "Timezone", "label.today": "Aujourd'hui", + "label.type": "Type", "label.unknown": "Unknown", "label.username": "Nom d'utilisateur", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Sites", "message.active-users": "{x} {x, plural, one {visiteur} other {visiteurs}} actuellement", "message.confirm-delete": "Êtes-vous sur de vouloir supprimer {target}?", @@ -57,6 +62,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/id-ID.json b/lang/id-ID.json index 8b09c0a6..8f12d762 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -26,6 +26,7 @@ "label.default-date-range": "Rentang tanggal default", "label.domain": "Domain", "label.enable-share-url": "Aktifkan URL berbagi", + "label.event": "Event", "label.invalid": "Tidak valid", "label.invalid-domain": "Domain tidak valid", "label.last-days": "{x} hari terakhir", @@ -34,6 +35,7 @@ "label.logout": "Keluar", "label.name": "Nama", "label.new-password": "Kata sandi baru", + "label.pageview": "Pageview", "label.password": "Kata sandi", "label.passwords-dont-match": "Kata sandi tidak cocok", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Tahun ini", "label.timezone": "Zona waktu", "label.today": "Hari ini", + "label.type": "Type", "label.unknown": "Tidak diketahui", "label.username": "Nama pengguna", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Situs web", "message.active-users": "{x} pengunjung saat ini", "message.confirm-delete": "Apakah kamu yakin ingin menghapus {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Dapatkan kode pelacakan", "message.go-to-settings": "Pergi ke pengaturan", "message.incorrect-username-password": "Nama pengguna/kata sandi salah.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Versi terbaru umami {version} telah tersedia!", "message.no-data-available": "Tidak ada data.", "message.no-websites-configured": "Anda tidak memiliki situs web yang dikonfigurasi.", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index bbf73ed5..fd276b26 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -26,6 +26,7 @@ "label.default-date-range": "最初に表示する期間", "label.domain": "ドメイン", "label.enable-share-url": "共有リンクを有効にする", + "label.event": "Event", "label.invalid": "無効", "label.invalid-domain": "無効なドメイン", "label.last-days": "過去{x}日間", @@ -34,6 +35,7 @@ "label.logout": "ログアウト", "label.name": "名前", "label.new-password": "新しいパスワード", + "label.pageview": "Pageview", "label.password": "パスワード", "label.passwords-dont-match": "パスワードが一致しません", "label.profile": "プロファイル", @@ -45,8 +47,11 @@ "label.this-year": "今年", "label.timezone": "タイムゾーン", "label.today": "今日", + "label.type": "Type", "label.unknown": "不明", "label.username": "ユーザー名", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Webサイト", "message.active-users": "{x}人が閲覧中です。", "message.confirm-delete": "{target}を削除してもよろしいですか?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "トラッキングコードを取得", "message.go-to-settings": "設定する", "message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "新しいバージョン({version})が利用可能です!", "message.no-data-available": "データがありません。", "message.no-websites-configured": "Webサイトが設定されていません。", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index 70af8f29..a6e632db 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -26,6 +26,7 @@ "label.default-date-range": "Өгөгдмөл хугацааны муж", "label.domain": "Домэйн", "label.enable-share-url": "Хуваалцах холбоос идэвхжүүлэх", + "label.event": "Event", "label.invalid": "Буруу", "label.invalid-domain": "Буруу домэйн", "label.last-days": "Сүүлийн {x} хоног", @@ -34,6 +35,7 @@ "label.logout": "Гарах", "label.name": "Нэр", "label.new-password": "Шинэ нууц үг", + "label.pageview": "Pageview", "label.password": "Нууц үг", "label.passwords-dont-match": "Нууц үг тохирохгүй байна", "label.profile": "Бүртгэл", @@ -45,8 +47,11 @@ "label.this-year": "Энэ жил", "label.timezone": "Цагийн бүс", "label.today": "Өнөөдөр", + "label.type": "Type", "label.unknown": "Тодорхойгүй", "label.username": "Хэрэглэгчийн нэр", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Вебүүд", "message.active-users": "одоо {x} {x, plural, one {зочин} other {зочин}} байна", "message.confirm-delete": "Та {target}-г устгахдаа итгэлтэй байна уу?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Мөрдөх код авах", "message.go-to-settings": "Тохиргоо руу очих", "message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Umami-гийн шинэ хувилбар {version} гарсан байна!", "message.no-data-available": "Өгөгдөл алга.", "message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.", diff --git a/lang/nb-NO.json b/lang/nb-NO.json index 8e93b05c..fc2664f9 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -26,6 +26,7 @@ "label.default-date-range": "Standard datoperiode", "label.domain": "Domene", "label.enable-share-url": "Aktiver delings-URL", + "label.event": "Event", "label.invalid": "Ugyldig", "label.invalid-domain": "Ugyldig domene", "label.last-days": "Siste {x} dager", @@ -34,6 +35,7 @@ "label.logout": "Logg ut", "label.name": "Navn", "label.new-password": "Nytt passord", + "label.pageview": "Pageview", "label.password": "Passord", "label.passwords-dont-match": "Passordene er ikke like", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "I år", "label.timezone": "Tidssone", "label.today": "I dag", + "label.type": "Type", "label.unknown": "Ukjent", "label.username": "Brukernavn", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Nettsteder", "message.active-users": "{x} {x, plural, one {besøkende} other {besøkende}} nå", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Få sporingskode", "message.go-to-settings": "Gå til innstillinger", "message.incorrect-username-password": "Ugyldig brukernavn/passord.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "En ny versjon av umami {version} er tilgjengelig!", "message.no-data-available": "Ingen data tilgjengelig.", "message.no-websites-configured": "Du har ikke satt opp noen nettsteder.", diff --git a/lang/nl-NL.json b/lang/nl-NL.json index 8978d7ee..cbedfc4c 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -26,6 +26,7 @@ "label.default-date-range": "Standaard bereik", "label.domain": "Domein", "label.enable-share-url": "Sta delen via openbare URL toe", + "label.event": "Event", "label.invalid": "Ongeldig", "label.invalid-domain": "Ongeldig domein", "label.last-days": "Laatste {x} dagen", @@ -34,6 +35,7 @@ "label.logout": "Uitloggen", "label.name": "Naam", "label.new-password": "Nieuw wachtwoord", + "label.pageview": "Pageview", "label.password": "Wachtwoord", "label.passwords-dont-match": "Wachtwoorden komen niet overeen", "label.profile": "Profiel", @@ -45,8 +47,11 @@ "label.this-year": "Dit jaar", "label.timezone": "Tijdzone", "label.today": "Vandaag", + "label.type": "Type", "label.unknown": "Onbekend", "label.username": "Gebruikersnaam", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} actieve {x, plural, one {bezoeker} other {bezoekers}}", "message.confirm-delete": "Weet je zeker dat je {target} wilt verwijderen?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Tracking code", "message.go-to-settings": "Naar instellingen", "message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Een nieuwe versie van umami {version} is beschikbaar!", "message.no-data-available": "Geen gegevens beschikbaar.", "message.no-websites-configured": "Je hebt geen websites ingesteld.", diff --git a/lang/pt-PT.json b/lang/pt-PT.json index 971c1aa7..ca31437b 100644 --- a/lang/pt-PT.json +++ b/lang/pt-PT.json @@ -26,6 +26,7 @@ "label.default-date-range": "Intervalo de datas predefinido", "label.domain": "Domínio", "label.enable-share-url": "Ativar link de partilha", + "label.event": "Event", "label.invalid": "Inválido", "label.invalid-domain": "Domínio inválido", "label.last-days": "Últimos {x} dias", @@ -34,6 +35,7 @@ "label.logout": "Sair", "label.name": "Nome", "label.new-password": "Nova palavra-passe", + "label.pageview": "Pageview", "label.password": "Palavra-passe", "label.passwords-dont-match": "Palavra-passes não correspondem", "label.profile": "Perfil", @@ -45,8 +47,11 @@ "label.this-year": "Este ano", "label.timezone": "Fuso horário", "label.today": "Hoje", + "label.type": "Type", "label.unknown": "Desconhecido", "label.username": "Nome de utilizador", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} {x, plural, one {visitante} other {visitantes}} neste momento", "message.confirm-delete": "Tens a certeza que queres eliminar {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Obter código de tracking", "message.go-to-settings": "Ir para as definições", "message.incorrect-username-password": "Nome de utilizador/palavra-passe incorretos.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Uma nova versão de umami {version} está disponível!", "message.no-data-available": "Sem dados disponíveis.", "message.no-websites-configured": "Não tens nenhum website configurado.", diff --git a/lang/ro-RO.json b/lang/ro-RO.json index 8e28f887..7103759a 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -26,6 +26,7 @@ "label.default-date-range": "Interval de date implicit", "label.domain": "Domeniu", "label.enable-share-url": "Activare adresa URL de distribuire", + "label.event": "Event", "label.invalid": "Invalid", "label.invalid-domain": "Invalid domain", "label.last-days": "Ultimele {x} zile", @@ -34,6 +35,7 @@ "label.logout": "Dezautentificare", "label.name": "Nume", "label.new-password": "Parola nouă", + "label.pageview": "Pageview", "label.password": "Parolă", "label.passwords-dont-match": "Parolele nu se potrivesc", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Acest an", "label.timezone": "Fus orar", "label.today": "Astăzi", + "label.type": "Type", "label.unknown": "Necunoscut", "label.username": "Username", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Site-uri web", "message.active-users": "{x} {x, plural, one {vizitator activ} other {vizitatori activi}}", "message.confirm-delete": "Sunteți sigur că doriți să ștergeți {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Obține codul de urmărire", "message.go-to-settings": "Mergi la Setări", "message.incorrect-username-password": "Username/parolă incorecte.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Este disponibilă o nouă versiune {version} de umami!", "message.no-data-available": "Nicio informație disponibilă.", "message.no-websites-configured": "Nu aveți niciun site web configurat.", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 05c9e0da..fb2c4088 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -26,6 +26,7 @@ "label.default-date-range": "Диапазон дат по-умолчанию", "label.domain": "Домен", "label.enable-share-url": "Разрешить делиться ссылкой", + "label.event": "Event", "label.invalid": "Некорректный", "label.invalid-domain": "Некорректный домен", "label.last-days": "Последние {x} дней", @@ -34,6 +35,7 @@ "label.logout": "Выйти", "label.name": "Имя", "label.new-password": "Новый пароль", + "label.pageview": "Pageview", "label.password": "Пароль", "label.passwords-dont-match": "Пароли не совпадают", "label.profile": "Профиль", @@ -45,8 +47,11 @@ "label.this-year": "Этот год", "label.timezone": "Часовой пояс", "label.today": "Сегодня", + "label.type": "Type", "label.unknown": "Неизвестно", "label.username": "Имя пользователя", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Сайты", "message.active-users": "{x} текущих посетителей", "message.confirm-delete": "Вы уверены, что хотите удалить {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Получить код отслеживания", "message.go-to-settings": "Перейти к настройкам", "message.incorrect-username-password": "Неверное имя пользователя/пароль.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Доступна новая версия umami {version}", "message.no-data-available": "Нет данных.", "message.no-websites-configured": "У вас нет настроенных сайтов.", diff --git a/lang/sv-SE.json b/lang/sv-SE.json index 5a1662a5..35e61fc7 100644 --- a/lang/sv-SE.json +++ b/lang/sv-SE.json @@ -26,6 +26,7 @@ "label.default-date-range": "Standard datum-urval", "label.domain": "Domän", "label.enable-share-url": "Aktivera delnings-URL", + "label.event": "Event", "label.invalid": "Ogiltig", "label.invalid-domain": "Ogiltig domän", "label.last-days": "Senaste {x} dagarna", @@ -34,6 +35,7 @@ "label.logout": "Logga ut", "label.name": "Namn", "label.new-password": "Nytt lösenord", + "label.pageview": "Pageview", "label.password": "Lösenord", "label.passwords-dont-match": "Lösenorden är inte samma", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Detta år", "label.timezone": "Tidszon", "label.today": "Idag", + "label.type": "Type", "label.unknown": "Okänd", "label.username": "Användarnamn", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Webbsajt", "message.active-users": "{x} {x, plural, one {besökare} other {besökare}} just nu", "message.confirm-delete": "Är du säker på att du vill radera {target}?", @@ -57,6 +62,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index b1b206f3..c16ba8e5 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -26,6 +26,7 @@ "label.default-date-range": "Varsayılan tarih aralığı", "label.domain": "Alan adı", "label.enable-share-url": "Anonim paylaşım URL'i aktif", + "label.event": "Event", "label.invalid": "Geçeriz", "label.invalid-domain": "Geçersiz alan adı", "label.last-days": "Son {x} gün", @@ -34,6 +35,7 @@ "label.logout": "Çıkış Yap", "label.name": "İsim", "label.new-password": "Yeni parola", + "label.pageview": "Pageview", "label.password": "Parola", "label.passwords-dont-match": "Parolalar uyuşmuyor", "label.profile": "Profil", @@ -45,8 +47,11 @@ "label.this-year": "Bu yıl", "label.timezone": "Zaman dilimi", "label.today": "Bugün", + "label.type": "Type", "label.unknown": "Bilinmeyen", "label.username": "Kullanıcı adı", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Web siteleri", "message.active-users": "{x} aktif ziyaretçi", "message.confirm-delete": "{target} kaydını silmek istediğinizden emin misiniz?", @@ -57,6 +62,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "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", diff --git a/lang/uk-UA.json b/lang/uk-UA.json index 4d077847..7f92162e 100644 --- a/lang/uk-UA.json +++ b/lang/uk-UA.json @@ -26,6 +26,7 @@ "label.default-date-range": "Діапазон дат за умовчанням", "label.domain": "Домен", "label.enable-share-url": "Дозволити ділитися посиланням", + "label.event": "Event", "label.invalid": "Некоректний", "label.invalid-domain": "Некоректний домен", "label.last-days": "Останні {x} днів", @@ -34,6 +35,7 @@ "label.logout": "Вийти", "label.name": "Ім'я", "label.new-password": "Новий пароль", + "label.pageview": "Pageview", "label.password": "Пароль", "label.passwords-dont-match": "Паролі не співпадають", "label.profile": "Профіль", @@ -45,8 +47,11 @@ "label.this-year": "Поточний рік", "label.timezone": "Часовий пояс", "label.today": "Сьогодні", + "label.type": "Type", "label.unknown": "Невідомо", "label.username": "Ім'я користувача", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "Веб-сайти", "message.active-users": "{x} поточних відвідувачів", "message.confirm-delete": "Ви впевнені, що бажаєте видалити {target}?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "Отримати код для відслідковування", "message.go-to-settings": "Перейти до налаштувань", "message.incorrect-username-password": "Невірне ім'я користувача або пароль.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Нова версія umami {version} доступна!", "message.no-data-available": "Немає даних.", "message.no-websites-configured": "У вас немає налаштованих веб-сайтів.", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 761ed17a..6f7150d9 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -26,6 +26,7 @@ "label.default-date-range": "默认日期范围", "label.domain": "域名", "label.enable-share-url": "激活共享链接", + "label.event": "Event", "label.invalid": "输入无效", "label.invalid-domain": "无效域名", "label.last-days": "最近 {x} 天", @@ -34,6 +35,7 @@ "label.logout": "退出", "label.name": "名字", "label.new-password": "新密码", + "label.pageview": "Pageview", "label.password": "密码", "label.passwords-dont-match": "密码不一致", "label.profile": "个人资料", @@ -45,8 +47,11 @@ "label.this-year": "今年", "label.timezone": "时区", "label.today": "今天", + "label.type": "Type", "label.unknown": "未知", "label.username": "用户名", + "label.visitor": "Visitor", + "label.website": "Website", "label.websites": "网站", "message.active-users": "当前在线 {x} 人", "message.confirm-delete": "你确定要删除{target}吗?", @@ -57,6 +62,7 @@ "message.get-tracking-code": "获得跟踪代码", "message.go-to-settings": "去设置", "message.incorrect-username-password": "用户名密码不正确.", + "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "A new version of umami {version} is available!", "message.no-data-available": "无可用数据.", "message.no-websites-configured": "你还没有设置任何网站.", From 5b8bfc9a32b380ebabc9f56367c2cc9b48b45edb Mon Sep 17 00:00:00 2001 From: endormi Date: Fri, 9 Oct 2020 16:57:14 +0300 Subject: [PATCH 39/71] Add finnish translation --- lang/fi-FI.json | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lang/fi-FI.json diff --git a/lang/fi-FI.json b/lang/fi-FI.json new file mode 100644 index 00000000..23f046d0 --- /dev/null +++ b/lang/fi-FI.json @@ -0,0 +1,97 @@ +{ + "button.add-account": "Lisää tili", + "button.add-website": "Lisää verkkosivu", + "button.back": "Takaisin", + "button.cancel": "Peruuta", + "button.change-password": "Vaihda salasana", + "button.copy-to-clipboard": "Kopioi leikepöydälle", + "button.date-range": "Ajanjakso", + "button.delete": "Poista", + "button.dismiss": "Hylkää", + "button.edit": "Muokkaa", + "button.login": "Kirjaudu sisään", + "button.more": "Lisää", + "button.refresh": "Päivitä", + "button.reset": "Nollaa", + "button.save": "Tallenna", + "button.single-day": "Yksi päivä", + "button.view-details": "Katso tiedot", + "label.accounts": "Tilit", + "label.administrator": "Järjestelmänvalvoja", + "label.confirm-password": "Vahvista salasana", + "label.current-password": "Nykyinen salasana", + "label.custom-range": "Mukautettu jakso", + "label.dashboard": "Dashboard", + "label.default-date-range": "Oletusajanjakso", + "label.domain": "Verkkotunnus", + "label.enable-share-url": "Ota jakamisen URL-osoite käyttöön", + "label.invalid": "Virheellinen", + "label.invalid-domain": "Virheellinen verkkotunnus", + "label.last-days": "Viimeisimmät {x} päivät", + "label.last-hours": "Viimeisimmät {x} tunnit", + "label.logged-in-as": "Kirjautuneena sisään nimellä {username}", + "label.logout": "Kirjaudu ulos", + "label.name": "Nimi", + "label.new-password": "Uusi salasana", + "label.password": "Salasana", + "label.passwords-dont-match": "Salasanat eivät täsmää", + "label.profile": "Profiili", + "label.required": "Vaaditaan", + "label.settings": "Asetukset", + "label.this-month": "Tämä kuukausi", + "label.this-week": "Tämä viikko", + "label.this-year": "Tämä vuosi", + "label.timezone": "Aikavyöhyke", + "label.today": "Tänään", + "label.unknown": "Tuntematon", + "label.username": "Käyttäjänimi", + "label.websites": "Verkkosivut", + "message.active-users": "{x} nykyinen {x, plural, yksi {visitor} muut {visitors}}", + "message.confirm-delete": "Haluatko varmasti poistaa {target}?", + "message.copied": "Kopioitu!", + "message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.", + "message.failure": "Jotain meni väärin.", + "message.get-share-url": "Hanki jakamisen URL-osoite", + "message.get-tracking-code": "Hanki seurantakoodi", + "message.go-to-settings": "Mene asetuksiin", + "message.incorrect-username-password": "Väärä käyttäjänimi/salasana.", + "message.new-version-available": "Uusi versio umamista {version} on käytettävissä!", + "message.no-data-available": "Tietoja ei ole käytettävissä.", + "message.no-websites-configured": "Sinulla ei ole määritettyjä verkkosivustoja.", + "message.page-not-found": "Sivua ei löydetty.", + "message.powered-by": "Voimanlähteenä {name}", + "message.save-success": "Tallennettu onnistuneesti.", + "message.share-url": "Tämä on julkisesti jaettu URL-osoitteelle {target}.", + "message.track-stats": "Jos haluat seurata kohteen {target} tilastoja, aseta seuraava koodi verkkosivustosi {head} osioon.", + "message.type-delete": "Kirjoita {delete} alla olevaan ruutuun vahvistaaksesi.", + "metrics.actions": "Toiminnat", + "metrics.average-visit-time": "Keskimääräinen vierailuaika", + "metrics.bounce-rate": "Välitön poistuminen", + "metrics.browsers": "Selaimet", + "metrics.countries": "Maat", + "metrics.device.desktop": "Pöytäkone", + "metrics.device.laptop": "Kannettava tietokone", + "metrics.device.mobile": "Mobiili", + "metrics.device.tablet": "Tabletti", + "metrics.devices": "Laitteet", + "metrics.events": "Tapahtumat", + "metrics.filter.combined": "Yhdistetty", + "metrics.filter.domain-only": "Vain verkkotunnus", + "metrics.filter.raw": "Käsittelemätön", + "metrics.operating-systems": "Käyttöjärjestelmät", + "metrics.page-views": "Sivun näyttökertoja", + "metrics.pages": "Sivut", + "metrics.referrers": "Viittaajat", + "metrics.unique-visitors": "Uniikit vierailijat", + "metrics.views": "Näyttökertoja", + "metrics.visitors": "Vierailijat", + "title.add-account": "Lisää tili", + "title.add-website": "Lisää verkkosivu", + "title.change-password": "Vaihda salasana", + "title.delete-account": "Poista tili", + "title.delete-website": "Poista verkkosivu", + "title.edit-account": "Muokkaa tiliä", + "title.edit-website": "Muokkaa verkkosivua", + "title.share-url": "Jaa URL", + "title.tracking-code": "Seurantakoodi" +} From 89843f62eef846c8ff5dbd6c72e547346e3dbc64 Mon Sep 17 00:00:00 2001 From: Vito Botta Date: Fri, 9 Oct 2020 20:48:09 +0300 Subject: [PATCH 40/71] Fix CORS error when IGNORE_IP is set --- pages/api/collect.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/api/collect.js b/pages/api/collect.js index 4f01b225..2ea528be 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -6,6 +6,8 @@ import { createToken } from 'lib/crypto'; import { getIpAddress } from '../../lib/request'; export default async (req, res) => { + await useCors(req, res); + if (isBot(req.headers['user-agent'])) { return ok(res); } @@ -19,7 +21,6 @@ export default async (req, res) => { } } - await useCors(req, res); await useSession(req, res); const { type, payload } = req.body; From 5dd3110fbf489ace3dbe4f8edf324fa3d5465c84 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 10:48:47 -0700 Subject: [PATCH 41/71] Updated log merge logic to prevent duplicates. --- components/common/Table.module.css | 1 + components/metrics/RealtimeLog.js | 2 +- components/pages/RealtimeDashboard.js | 22 ++++++++++-------- pages/api/collect.js | 3 ++- pages/api/realtime.js | 33 +++++++++++++++++++++++---- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/components/common/Table.module.css b/components/common/Table.module.css index bde2588a..5db753ec 100644 --- a/components/common/Table.module.css +++ b/components/common/Table.module.css @@ -21,6 +21,7 @@ } .row { + display: flex; border-bottom: 1px solid var(--gray300); padding: 10px 0; } diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index fce9f7e9..9164dcdb 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -103,7 +103,7 @@ export default function RealtimeLog({ data, websites }) { return ( ); diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 348b6642..af3fdc58 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -11,20 +11,22 @@ import RealtimeLog from '../metrics/RealtimeLog'; const REALTIME_RANGE = 30; const REALTIME_INTERVAL = 5000; -function filterTime(data, time) { - return data.filter(({ created_at }) => new Date(created_at).getTime() >= time); +function mergeData(state, data, time) { + const ids = state.map(({ __id }) => __id); + return state + .concat(data.filter(({ __id }) => !ids.includes(__id))) + .filter(({ created_at }) => new Date(created_at).getTime() >= time); } export default function RealtimeDashboard() { const [data, setData] = useState(); const [website, setWebsite] = useState(); - const [lastTime, setLastTime] = useState(); const { data: init, loading } = useFetch('/api/realtime', { type: 'init' }); const { data: updates } = useFetch( '/api/realtime', - { type: 'update', start_at: lastTime }, + { type: 'update', start_at: data?.timestamp }, { - disabled: !init?.token, + disabled: !init?.token || !data, interval: REALTIME_INTERVAL, headers: { 'x-umami-token': init?.token }, }, @@ -34,15 +36,15 @@ export default function RealtimeDashboard() { if (init && !data) { setData(init.data); } else if (updates) { - const { pageviews, sessions, events } = updates; + const { pageviews, sessions, events, timestamp } = updates; const minTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); setData(state => ({ - pageviews: filterTime(state.pageviews.concat(pageviews), minTime), - sessions: filterTime(state.sessions.concat(sessions), minTime), - events: filterTime(state.events.concat(events), minTime), + pageviews: mergeData(state.pageviews, pageviews, minTime), + sessions: mergeData(state.sessions, sessions, minTime), + events: mergeData(state.events, events, minTime), + timestamp, })); } - setLastTime(Date.now()); }, [init, updates]); if (!init || loading || !data) { diff --git a/pages/api/collect.js b/pages/api/collect.js index 4f01b225..a6140169 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -6,6 +6,8 @@ import { createToken } from 'lib/crypto'; import { getIpAddress } from '../../lib/request'; export default async (req, res) => { + await useCors(req, res); + if (isBot(req.headers['user-agent'])) { return ok(res); } @@ -19,7 +21,6 @@ export default async (req, res) => { } } - await useCors(req, res); await useSession(req, res); const { type, payload } = req.body; diff --git a/pages/api/realtime.js b/pages/api/realtime.js index 64cbea5e..538f611a 100644 --- a/pages/api/realtime.js +++ b/pages/api/realtime.js @@ -8,11 +8,30 @@ export default async (req, res) => { await useAuth(req, res); async function getData(websites, time) { - return Promise.all([ + const [pageviews, sessions, events] = await Promise.all([ getPageviews(websites, time), getSessions(websites, time), getEvents(websites, time), ]); + + return { + pageviews: pageviews.map(({ view_id, ...props }) => ({ + __id: `p${view_id}`, + view_id, + ...props, + })), + sessions: sessions.map(({ session_id, ...props }) => ({ + __id: `s${session_id}`, + session_id, + ...props, + })), + events: events.map(({ event_id, ...props }) => ({ + __id: `e${event_id}`, + event_id, + ...props, + })), + timestamp: Date.now(), + }; } if (req.method === 'GET') { @@ -22,10 +41,14 @@ export default async (req, res) => { 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 }); + const data = await getData(ids, subMinutes(new Date(), 30)); - return ok(res, { websites, token, data: { pageviews, sessions, events } }); + return ok(res, { + websites, + token, + data, + }); } if (type === 'update') { @@ -37,9 +60,9 @@ export default async (req, res) => { const { websites } = await parseToken(token); - const [pageviews, sessions, events] = await getData(websites, new Date(+start_at)); + const data = await getData(websites, new Date(+start_at)); - return ok(res, { pageviews, sessions, events }); + return ok(res, data); } return badRequest(res); From 0ccae7483ca842804ac6a867c6ba99cc3b3ff4e3 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 11:14:42 -0700 Subject: [PATCH 42/71] Update realtime chart animation. --- components/metrics/BarChart.js | 2 +- components/metrics/RealtimeChart.js | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js index 24f642c1..f32691fa 100644 --- a/components/metrics/BarChart.js +++ b/components/metrics/BarChart.js @@ -40,7 +40,7 @@ export default function BarChart({ switch (unit) { case 'minute': - return dateFormat(d, 'h:mm', locale); + return index % 2 === 0 ? dateFormat(d, 'h:mm', locale) : ''; case 'hour': return dateFormat(d, 'ha', locale); case 'day': diff --git a/components/metrics/RealtimeChart.js b/components/metrics/RealtimeChart.js index b27322ca..b4703036 100644 --- a/components/metrics/RealtimeChart.js +++ b/components/metrics/RealtimeChart.js @@ -1,5 +1,5 @@ -import React, { useMemo } from 'react'; -import { format, parseISO, startOfMinute, subMinutes } from 'date-fns'; +import React, { useMemo, useRef } from 'react'; +import { format, parseISO, startOfMinute, subMinutes, isBefore } from 'date-fns'; import PageviewsChart from './PageviewsChart'; import { getDateArray } from 'lib/date'; @@ -23,13 +23,13 @@ function mapData(data) { return arr; } -export default function RealtimeChart({ data, ...props }) { +export default function RealtimeChart({ data, unit, ...props }) { + const endDate = startOfMinute(new Date()); + const startDate = subMinutes(endDate, 30); + const prevEndDate = useRef(endDate); + const chartData = useMemo(() => { if (data) { - const endDate = startOfMinute(new Date()); - const startDate = subMinutes(endDate, 30); - const unit = 'minute'; - return { pageviews: getDateArray(mapData(data.pageviews), startDate, endDate, unit), sessions: getDateArray(mapData(data.sessions), startDate, endDate, unit), @@ -38,5 +38,16 @@ export default function RealtimeChart({ data, ...props }) { return { pageviews: [], sessions: [] }; }, [data]); - return ; + // Don't animate the bars shifting over because it looks weird + const animationDuration = useMemo(() => { + if (isBefore(prevEndDate.current, endDate)) { + prevEndDate.current = endDate; + return 0; + } + return 300; + }, [data]); + + return ( + + ); } From e30f2dfb44ec334b63da87cd0ac306038a526824 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 17:58:27 -0700 Subject: [PATCH 43/71] Realtime header component. --- components/common/Table.js | 25 ++++---- components/common/Table.module.css | 2 - components/common/Tag.module.css | 1 - components/metrics/BarChart.js | 8 +-- components/metrics/PageviewsChart.js | 2 + components/metrics/RealtimeChart.js | 8 ++- components/metrics/RealtimeHeader.js | 53 ++++++++++++++++ components/metrics/RealtimeHeader.module.css | 3 + components/metrics/RealtimeLog.js | 62 ++++++++++--------- components/metrics/RealtimeLog.module.css | 18 +++++- components/pages/RealtimeDashboard.js | 57 ++++++++--------- components/pages/RealtimeDashboard.module.css | 4 ++ lang/da-DK.json | 5 -- lang/de-DE.json | 5 -- lang/el-GR.json | 5 -- lang/en-US.json | 5 -- lang/es-MX.json | 5 -- lang/fi-FI.json | 3 + lang/fo-FO.json | 5 -- lang/fr-FR.json | 5 -- lang/id-ID.json | 5 -- lang/ja-JP.json | 5 -- lang/mn-MN.json | 5 -- lang/nb-NO.json | 5 -- lang/nl-NL.json | 5 -- lang/pt-PT.json | 5 -- lang/ro-RO.json | 5 -- lang/ru-RU.json | 5 -- lang/sv-SE.json | 5 -- lang/tr-TR.json | 5 -- lang/uk-UA.json | 5 -- lang/zh-CN.json | 5 -- lib/constants.js | 1 + package.json | 2 +- 34 files changed, 167 insertions(+), 177 deletions(-) create mode 100644 components/metrics/RealtimeHeader.js create mode 100644 components/metrics/RealtimeHeader.module.css diff --git a/components/common/Table.js b/components/common/Table.js index d0a1e0c7..f0989903 100644 --- a/components/common/Table.js +++ b/components/common/Table.js @@ -10,6 +10,7 @@ export default function Table({ className, bodyClassName, rowKey, + showHeader = true, children, }) { if (empty && rows.length === 0) { @@ -18,17 +19,19 @@ export default function Table({ return (
-
- {columns.map(({ key, label, className, style, header }) => ( -
- {label} -
- ))} -
+ {showHeader && ( +
+ {columns.map(({ key, label, className, style, header }) => ( +
+ {label} +
+ ))} +
+ )}
{rows.length === 0 && } {!children && diff --git a/components/common/Table.module.css b/components/common/Table.module.css index 5db753ec..4d7cd24b 100644 --- a/components/common/Table.module.css +++ b/components/common/Table.module.css @@ -4,7 +4,6 @@ } .header { - display: flex; border-bottom: 1px solid var(--gray300); } @@ -21,7 +20,6 @@ } .row { - display: flex; border-bottom: 1px solid var(--gray300); padding: 10px 0; } diff --git a/components/common/Tag.module.css b/components/common/Tag.module.css index 38d66692..5e145ea1 100644 --- a/components/common/Tag.module.css +++ b/components/common/Tag.module.css @@ -1,5 +1,4 @@ .tag { - font-size: var(--font-size-small); padding: 2px 4px; border: 1px solid var(--gray300); border-radius: 4px; diff --git a/components/metrics/BarChart.js b/components/metrics/BarChart.js index f32691fa..05183e1b 100644 --- a/components/metrics/BarChart.js +++ b/components/metrics/BarChart.js @@ -5,17 +5,17 @@ import ChartJS from 'chart.js'; import { formatLongNumber } from 'lib/format'; import { dateFormat } from 'lib/lang'; import useLocale from 'hooks/useLocale'; -import styles from './BarChart.module.css'; import useTheme from 'hooks/useTheme'; -import { THEME_COLORS } from 'lib/constants'; +import { DEFAUL_CHART_HEIGHT, DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants'; +import styles from './BarChart.module.css'; export default function BarChart({ chartId, datasets, unit, records, - height = 400, - animationDuration = 300, + height = DEFAUL_CHART_HEIGHT, + animationDuration = DEFAULT_ANIMATION_DURATION, className, stacked = false, loading = false, diff --git a/components/metrics/PageviewsChart.js b/components/metrics/PageviewsChart.js index 30e85e80..79fe4917 100644 --- a/components/metrics/PageviewsChart.js +++ b/components/metrics/PageviewsChart.js @@ -14,6 +14,7 @@ export default function PageviewsChart({ className, loading, animationDuration = DEFAULT_ANIMATION_DURATION, + ...props }) { const intl = useIntl(); const [theme] = useTheme(); @@ -56,6 +57,7 @@ export default function PageviewsChart({ {visible => ( + ); } diff --git a/components/metrics/RealtimeHeader.js b/components/metrics/RealtimeHeader.js new file mode 100644 index 00000000..09f83ff6 --- /dev/null +++ b/components/metrics/RealtimeHeader.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import PageHeader from '../layout/PageHeader'; +import DropDown from '../common/DropDown'; +import MetricCard from './MetricCard'; +import styles from './RealtimeHeader.module.css'; + +export default function RealtimeHeader({ websites, data, websiteId, onSelect }) { + const options = [ + { label: , value: 0 }, + ].concat(websites.map(({ name, website_id }) => ({ label: name, value: website_id }))); + + const { pageviews, sessions, events } = data; + const countries = sessions.reduce((obj, { country }) => { + if (country) { + if (!obj[country]) { + obj[country] = 1; + } else { + obj[country] += 1; + } + } + return obj; + }, {}); + + return ( + <> + +
+ +
+ +
+
+ } + value={pageviews.length} + /> + } + value={sessions.length} + /> + } + value={events.length} + /> + } + value={Object.keys(countries).length} + /> +
+ + ); +} diff --git a/components/metrics/RealtimeHeader.module.css b/components/metrics/RealtimeHeader.module.css new file mode 100644 index 00000000..8f948eb1 --- /dev/null +++ b/components/metrics/RealtimeHeader.module.css @@ -0,0 +1,3 @@ +.metrics { + display: flex; +} diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 1ea0080e..6fddf71b 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import { FixedSizeList } from 'react-window'; +import classNames from 'classnames'; import firstBy from 'thenby'; import { format } from 'date-fns'; import Icon from 'components/common/Icon'; @@ -14,8 +15,17 @@ import Visitor from 'assets/visitor.svg'; import Eye from 'assets/eye.svg'; import styles from './RealtimeLog.module.css'; +const TYPE_PAGEVIEW = 0; +const TYPE_SESSION = 1; +const TYPE_EVENT = 2; + +const TYPE_ICONS = { + [TYPE_PAGEVIEW]: , + [TYPE_SESSION]: , + [TYPE_EVENT]: , +}; + export default function RealtimeLog({ data, websites }) { - const intl = useIntl(); const [locale] = useLocale(); const countryNames = useCountryNames(locale); const logs = useMemo(() => { @@ -26,24 +36,21 @@ export default function RealtimeLog({ data, websites }) { const columns = [ { key: 'time', - label: , - className: 'col-1', - render: ({ created_at }) => format(new Date(created_at), 'H:mm:ss'), + className: classNames(styles.time, 'col-3 col-lg-1'), + render: ({ created_at }) => format(new Date(created_at), 'h:mm:ss'), }, { key: 'website', - label: , - className: 'col-2', + className: classNames(styles.website, 'col-9 col-lg-2'), render: getWebsite, }, { - key: 'type', - label: , - className: 'col-9', + key: 'detail', + className: classNames(styles.detail, 'col-12 col-lg-9'), render: row => ( <> - - {getDescription(row)} + + {getDetail(row)} ), }, @@ -51,35 +58,26 @@ export default function RealtimeLog({ data, websites }) { function getType({ view_id, session_id, event_id }) { if (event_id) { - return intl.formatMessage({ id: 'label.event', defaultMessage: 'Event' }); + return TYPE_EVENT; } if (view_id) { - return intl.formatMessage({ id: 'label.pageview', defaultMessage: 'Pageview' }); + return TYPE_PAGEVIEW; } if (session_id) { - return intl.formatMessage({ id: 'label.visitor', defaultMessage: 'Visitor' }); + return TYPE_SESSION; } return null; } - function getIcon({ view_id, session_id, event_id }) { - if (event_id) { - return ; - } - if (view_id) { - return ; - } - if (session_id) { - return ; - } - return null; + function getIcon(row) { + return TYPE_ICONS[getType(row)]; } function getWebsite({ website_id }) { return websites.find(n => n.website_id === website_id)?.name; } - function getDescription({ + function getDetail({ event_type, event_value, view_id, @@ -121,8 +119,14 @@ export default function RealtimeLog({ data, websites }) { return (
- - +
+ {Row}
diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index 5227be98..af100708 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -1,5 +1,5 @@ .table { - font-size: var(--font-size-small); + font-size: var(--font-size-xsmall); } .row { @@ -16,3 +16,19 @@ align-self: center; margin-right: 20px; } + +.website { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.detail { + flex: 1; +} + +.detail span { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index bb2948bc..fd123f8b 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -1,12 +1,12 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; import { subMinutes, startOfMinute } 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 RealtimeChart from '../metrics/RealtimeChart'; import RealtimeLog from '../metrics/RealtimeLog'; +import styles from './RealtimeDashboard.module.css'; +import RealtimeHeader from '../metrics/RealtimeHeader'; const REALTIME_RANGE = 30; const REALTIME_INTERVAL = 5000; @@ -24,28 +24,27 @@ function filterWebsite(data, id) { export default function RealtimeDashboard() { const [data, setData] = useState(); - const [website, setWebsite] = useState(); + const [websiteId, setWebsiteId] = useState(0); const { data: init, loading } = useFetch('/api/realtime', { params: { type: 'init' } }); const { data: updates } = useFetch('/api/realtime', { params: { type: 'update', start_at: data?.timestamp }, - disabled: !init?.token || !data, + disabled: !init?.websites?.length || !data, interval: REALTIME_INTERVAL, headers: { 'x-umami-token': init?.token }, }); const realtimeData = useMemo(() => { - if (website) { - const { website_id } = website; + if (websiteId) { const { pageviews, sessions, events, ...props } = data; return { - pageviews: filterWebsite(pageviews, website_id), - sessions: filterWebsite(sessions, website_id), - events: filterWebsite(events, website_id), + pageviews: filterWebsite(pageviews, websiteId), + sessions: filterWebsite(sessions, websiteId), + events: filterWebsite(events, websiteId), ...props, }; } return data; - }, [data, website]); + }, [data, websiteId]); useEffect(() => { if (init && !data) { @@ -68,33 +67,27 @@ export default function RealtimeDashboard() { 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(websites.find(({ website_id }) => website_id === value)); - } - return ( - -
- -
- -
- -
-
+
+ +
+
+
+
hi.
); diff --git a/components/pages/RealtimeDashboard.module.css b/components/pages/RealtimeDashboard.module.css index abde53a3..b9b5a632 100644 --- a/components/pages/RealtimeDashboard.module.css +++ b/components/pages/RealtimeDashboard.module.css @@ -1,3 +1,7 @@ .container { display: flex; } + +.chart { + margin-bottom: 30px; +} diff --git a/lang/da-DK.json b/lang/da-DK.json index 9d33fd7b..5532bf69 100644 --- a/lang/da-DK.json +++ b/lang/da-DK.json @@ -26,7 +26,6 @@ "label.default-date-range": "Default date range", "label.domain": "Domæne", "label.enable-share-url": "Aktivér delings-URL", - "label.event": "Event", "label.invalid": "Ugyldig", "label.invalid-domain": "Ugyldigt domæne", "label.last-days": "Sidste {x} dage", @@ -35,7 +34,6 @@ "label.logout": "Log ud", "label.name": "Navn", "label.new-password": "Ny adgangskode", - "label.pageview": "Pageview", "label.password": "Adgangskode", "label.passwords-dont-match": "Adgangskoder matcher ikke", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Dette år", "label.timezone": "Timezone", "label.today": "Idag", - "label.type": "Type", "label.unknown": "Ukendt", "label.username": "Brugernavn", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Hjemmesider", "message.active-users": "{x} nuværende {x, plural, one {bruger} other {brugere}}", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", diff --git a/lang/de-DE.json b/lang/de-DE.json index 7821f826..df5e6dd1 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -26,7 +26,6 @@ "label.default-date-range": "Voreingestellter Datumsbereich", "label.domain": "Domain", "label.enable-share-url": "Freigabe-URL aktivieren", - "label.event": "Event", "label.invalid": "Ungültig", "label.invalid-domain": "Ungültige Domain", "label.last-days": "Letzten {x} Tage", @@ -35,7 +34,6 @@ "label.logout": "Abmelden", "label.name": "Name", "label.new-password": "Neues Passwort", - "label.pageview": "Pageview", "label.password": "Passwort", "label.passwords-dont-match": "Passwörter stimmen nicht überein", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Dieses Jahr", "label.timezone": "Zeitzone", "label.today": "Heute", - "label.type": "Type", "label.unknown": "Unbekannt", "label.username": "Benutzername", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Webseiten", "message.active-users": "{x} {x, plural, one {aktiver Besucher} other {aktive Besucher}}", "message.confirm-delete": "Sind sie sich sicher {target} zu löschen?", diff --git a/lang/el-GR.json b/lang/el-GR.json index 88d4a0e1..7fbdbfbf 100644 --- a/lang/el-GR.json +++ b/lang/el-GR.json @@ -26,7 +26,6 @@ "label.default-date-range": "Προεπιλεγμένο εύρος ημερομηνιών", "label.domain": "Τομέας", "label.enable-share-url": "Ενεργοποίηση κοινής χρήσης URL", - "label.event": "Event", "label.invalid": "Μη έγκυρο", "label.invalid-domain": "Μη έγκυρος τομέας", "label.last-days": "Τελευταίες {x} ημέρες", @@ -35,7 +34,6 @@ "label.logout": "Αποσύνδεση", "label.name": "Όνομα", "label.new-password": "Νέος κωδικός", - "label.pageview": "Pageview", "label.password": "Κωδικός", "label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν", "label.profile": "Προφίλ", @@ -47,11 +45,8 @@ "label.this-year": "Αυτή την χρονιά", "label.timezone": "Ζώνη ώρας", "label.today": "Σήμερα", - "label.type": "Type", "label.unknown": "Άγνωστο", "label.username": "Όνομα χρήστη", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Ιστότοποι", "message.active-users": "{x} ενεργοί {x, plural, one {επισκέπτης} other {επισκέπτες}}", "message.confirm-delete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {target};", diff --git a/lang/en-US.json b/lang/en-US.json index cadd0512..07670232 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -26,7 +26,6 @@ "label.default-date-range": "Default date range", "label.domain": "Domain", "label.enable-share-url": "Enable share URL", - "label.event": "Event", "label.invalid": "Invalid", "label.invalid-domain": "Invalid domain", "label.last-days": "Last {x} days", @@ -35,7 +34,6 @@ "label.logout": "Logout", "label.name": "Name", "label.new-password": "New password", - "label.pageview": "Pageview", "label.password": "Password", "label.passwords-dont-match": "Passwords don't match", "label.profile": "Profile", @@ -47,11 +45,8 @@ "label.this-year": "This year", "label.timezone": "Timezone", "label.today": "Today", - "label.type": "Type", "label.unknown": "Unknown", "label.username": "Username", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}", "message.confirm-delete": "Are your sure you want to delete {target}?", diff --git a/lang/es-MX.json b/lang/es-MX.json index 48efe589..61395fcf 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -26,7 +26,6 @@ "label.default-date-range": "Default date range", "label.domain": "Dominio", "label.enable-share-url": "Habilitar compartir URL", - "label.event": "Event", "label.invalid": "Inválido", "label.invalid-domain": "Dominio inválido", "label.last-days": "Últimos {x} días", @@ -35,7 +34,6 @@ "label.logout": "Cerrar sesión", "label.name": "Nombre", "label.new-password": "Nueva contraseña", - "label.pageview": "Pageview", "label.password": "Contraseña", "label.passwords-dont-match": "Las contraseñas no coinciden", "label.profile": "Perfil", @@ -47,11 +45,8 @@ "label.this-year": "Este año", "label.timezone": "Timezone", "label.today": "Hoy", - "label.type": "Type", "label.unknown": "Unknown", "label.username": "Nombre de usuario", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Sitios", "message.active-users": "{x} {x, plural, one {activo} other {activos}}", "message.confirm-delete": "¿Estás seguro(a) de querer eliminar {target}?", diff --git a/lang/fi-FI.json b/lang/fi-FI.json index 23f046d0..9f0c930b 100644 --- a/lang/fi-FI.json +++ b/lang/fi-FI.json @@ -18,6 +18,7 @@ "button.view-details": "Katso tiedot", "label.accounts": "Tilit", "label.administrator": "Järjestelmänvalvoja", + "label.all-websites": "All websites", "label.confirm-password": "Vahvista salasana", "label.current-password": "Nykyinen salasana", "label.custom-range": "Mukautettu jakso", @@ -36,6 +37,7 @@ "label.password": "Salasana", "label.passwords-dont-match": "Salasanat eivät täsmää", "label.profile": "Profiili", + "label.realtime": "Realtime", "label.required": "Vaaditaan", "label.settings": "Asetukset", "label.this-month": "Tämä kuukausi", @@ -55,6 +57,7 @@ "message.get-tracking-code": "Hanki seurantakoodi", "message.go-to-settings": "Mene asetuksiin", "message.incorrect-username-password": "Väärä käyttäjänimi/salasana.", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Uusi versio umamista {version} on käytettävissä!", "message.no-data-available": "Tietoja ei ole käytettävissä.", "message.no-websites-configured": "Sinulla ei ole määritettyjä verkkosivustoja.", diff --git a/lang/fo-FO.json b/lang/fo-FO.json index 2969b83e..ee5ab8cb 100644 --- a/lang/fo-FO.json +++ b/lang/fo-FO.json @@ -26,7 +26,6 @@ "label.default-date-range": "Standard dato", "label.domain": "Økisnavn", "label.enable-share-url": "Virkja deili leinki", - "label.event": "Event", "label.invalid": "Ógilda", "label.invalid-domain": "Ógilt økisnavn", "label.last-days": "Seinastu {x} dagarnar", @@ -35,7 +34,6 @@ "label.logout": "Rita út", "label.name": "Navn", "label.new-password": "Nýtt loyniorð", - "label.pageview": "Pageview", "label.password": "Loyniorð", "label.passwords-dont-match": "Loyniorðini eru ikki eins", "label.profile": "Brúkari", @@ -47,11 +45,8 @@ "label.this-year": "Hetta árið", "label.timezone": "Tíðarsona", "label.today": "Í dag", - "label.type": "Type", "label.unknown": "Ókent", "label.username": "Brúkaranavn", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Heimasíður", "message.active-users": "{x} í løtuni {x, plural, one {vitjandi} other { vitjandi }}", "message.confirm-delete": "Ert tú sikkur at tú ynskir at sletta {target}?", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index b62ce587..cb2cc103 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -26,7 +26,6 @@ "label.default-date-range": "Default date range", "label.domain": "Domaine", "label.enable-share-url": "Activer le partage d'URL", - "label.event": "Event", "label.invalid": "Invalide", "label.invalid-domain": "Domaine invalide", "label.last-days": "{x} derniers jours", @@ -35,7 +34,6 @@ "label.logout": "Déconnexion", "label.name": "Nom", "label.new-password": "Nouveau mot de passe", - "label.pageview": "Pageview", "label.password": "Mot de passe", "label.passwords-dont-match": "Les mots de passe ne correspondent pas", "label.profile": "Profile", @@ -47,11 +45,8 @@ "label.this-year": "Cette année", "label.timezone": "Timezone", "label.today": "Aujourd'hui", - "label.type": "Type", "label.unknown": "Unknown", "label.username": "Nom d'utilisateur", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Sites", "message.active-users": "{x} {x, plural, one {visiteur} other {visiteurs}} actuellement", "message.confirm-delete": "Êtes-vous sur de vouloir supprimer {target}?", diff --git a/lang/id-ID.json b/lang/id-ID.json index 8f12d762..7c7db298 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -26,7 +26,6 @@ "label.default-date-range": "Rentang tanggal default", "label.domain": "Domain", "label.enable-share-url": "Aktifkan URL berbagi", - "label.event": "Event", "label.invalid": "Tidak valid", "label.invalid-domain": "Domain tidak valid", "label.last-days": "{x} hari terakhir", @@ -35,7 +34,6 @@ "label.logout": "Keluar", "label.name": "Nama", "label.new-password": "Kata sandi baru", - "label.pageview": "Pageview", "label.password": "Kata sandi", "label.passwords-dont-match": "Kata sandi tidak cocok", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Tahun ini", "label.timezone": "Zona waktu", "label.today": "Hari ini", - "label.type": "Type", "label.unknown": "Tidak diketahui", "label.username": "Nama pengguna", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Situs web", "message.active-users": "{x} pengunjung saat ini", "message.confirm-delete": "Apakah kamu yakin ingin menghapus {target}?", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index fd276b26..09458224 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -26,7 +26,6 @@ "label.default-date-range": "最初に表示する期間", "label.domain": "ドメイン", "label.enable-share-url": "共有リンクを有効にする", - "label.event": "Event", "label.invalid": "無効", "label.invalid-domain": "無効なドメイン", "label.last-days": "過去{x}日間", @@ -35,7 +34,6 @@ "label.logout": "ログアウト", "label.name": "名前", "label.new-password": "新しいパスワード", - "label.pageview": "Pageview", "label.password": "パスワード", "label.passwords-dont-match": "パスワードが一致しません", "label.profile": "プロファイル", @@ -47,11 +45,8 @@ "label.this-year": "今年", "label.timezone": "タイムゾーン", "label.today": "今日", - "label.type": "Type", "label.unknown": "不明", "label.username": "ユーザー名", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Webサイト", "message.active-users": "{x}人が閲覧中です。", "message.confirm-delete": "{target}を削除してもよろしいですか?", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index a6e632db..5e92c9c7 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -26,7 +26,6 @@ "label.default-date-range": "Өгөгдмөл хугацааны муж", "label.domain": "Домэйн", "label.enable-share-url": "Хуваалцах холбоос идэвхжүүлэх", - "label.event": "Event", "label.invalid": "Буруу", "label.invalid-domain": "Буруу домэйн", "label.last-days": "Сүүлийн {x} хоног", @@ -35,7 +34,6 @@ "label.logout": "Гарах", "label.name": "Нэр", "label.new-password": "Шинэ нууц үг", - "label.pageview": "Pageview", "label.password": "Нууц үг", "label.passwords-dont-match": "Нууц үг тохирохгүй байна", "label.profile": "Бүртгэл", @@ -47,11 +45,8 @@ "label.this-year": "Энэ жил", "label.timezone": "Цагийн бүс", "label.today": "Өнөөдөр", - "label.type": "Type", "label.unknown": "Тодорхойгүй", "label.username": "Хэрэглэгчийн нэр", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Вебүүд", "message.active-users": "одоо {x} {x, plural, one {зочин} other {зочин}} байна", "message.confirm-delete": "Та {target}-г устгахдаа итгэлтэй байна уу?", diff --git a/lang/nb-NO.json b/lang/nb-NO.json index fc2664f9..0af11471 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -26,7 +26,6 @@ "label.default-date-range": "Standard datoperiode", "label.domain": "Domene", "label.enable-share-url": "Aktiver delings-URL", - "label.event": "Event", "label.invalid": "Ugyldig", "label.invalid-domain": "Ugyldig domene", "label.last-days": "Siste {x} dager", @@ -35,7 +34,6 @@ "label.logout": "Logg ut", "label.name": "Navn", "label.new-password": "Nytt passord", - "label.pageview": "Pageview", "label.password": "Passord", "label.passwords-dont-match": "Passordene er ikke like", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "I år", "label.timezone": "Tidssone", "label.today": "I dag", - "label.type": "Type", "label.unknown": "Ukjent", "label.username": "Brukernavn", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Nettsteder", "message.active-users": "{x} {x, plural, one {besøkende} other {besøkende}} nå", "message.confirm-delete": "Er du sikker på at du vil slette {target}?", diff --git a/lang/nl-NL.json b/lang/nl-NL.json index cbedfc4c..4613ed33 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -26,7 +26,6 @@ "label.default-date-range": "Standaard bereik", "label.domain": "Domein", "label.enable-share-url": "Sta delen via openbare URL toe", - "label.event": "Event", "label.invalid": "Ongeldig", "label.invalid-domain": "Ongeldig domein", "label.last-days": "Laatste {x} dagen", @@ -35,7 +34,6 @@ "label.logout": "Uitloggen", "label.name": "Naam", "label.new-password": "Nieuw wachtwoord", - "label.pageview": "Pageview", "label.password": "Wachtwoord", "label.passwords-dont-match": "Wachtwoorden komen niet overeen", "label.profile": "Profiel", @@ -47,11 +45,8 @@ "label.this-year": "Dit jaar", "label.timezone": "Tijdzone", "label.today": "Vandaag", - "label.type": "Type", "label.unknown": "Onbekend", "label.username": "Gebruikersnaam", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} actieve {x, plural, one {bezoeker} other {bezoekers}}", "message.confirm-delete": "Weet je zeker dat je {target} wilt verwijderen?", diff --git a/lang/pt-PT.json b/lang/pt-PT.json index ca31437b..81e3a334 100644 --- a/lang/pt-PT.json +++ b/lang/pt-PT.json @@ -26,7 +26,6 @@ "label.default-date-range": "Intervalo de datas predefinido", "label.domain": "Domínio", "label.enable-share-url": "Ativar link de partilha", - "label.event": "Event", "label.invalid": "Inválido", "label.invalid-domain": "Domínio inválido", "label.last-days": "Últimos {x} dias", @@ -35,7 +34,6 @@ "label.logout": "Sair", "label.name": "Nome", "label.new-password": "Nova palavra-passe", - "label.pageview": "Pageview", "label.password": "Palavra-passe", "label.passwords-dont-match": "Palavra-passes não correspondem", "label.profile": "Perfil", @@ -47,11 +45,8 @@ "label.this-year": "Este ano", "label.timezone": "Fuso horário", "label.today": "Hoje", - "label.type": "Type", "label.unknown": "Desconhecido", "label.username": "Nome de utilizador", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Websites", "message.active-users": "{x} {x, plural, one {visitante} other {visitantes}} neste momento", "message.confirm-delete": "Tens a certeza que queres eliminar {target}?", diff --git a/lang/ro-RO.json b/lang/ro-RO.json index 7103759a..7e2f4800 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -26,7 +26,6 @@ "label.default-date-range": "Interval de date implicit", "label.domain": "Domeniu", "label.enable-share-url": "Activare adresa URL de distribuire", - "label.event": "Event", "label.invalid": "Invalid", "label.invalid-domain": "Invalid domain", "label.last-days": "Ultimele {x} zile", @@ -35,7 +34,6 @@ "label.logout": "Dezautentificare", "label.name": "Nume", "label.new-password": "Parola nouă", - "label.pageview": "Pageview", "label.password": "Parolă", "label.passwords-dont-match": "Parolele nu se potrivesc", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Acest an", "label.timezone": "Fus orar", "label.today": "Astăzi", - "label.type": "Type", "label.unknown": "Necunoscut", "label.username": "Username", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Site-uri web", "message.active-users": "{x} {x, plural, one {vizitator activ} other {vizitatori activi}}", "message.confirm-delete": "Sunteți sigur că doriți să ștergeți {target}?", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index fb2c4088..8a1ffb1d 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -26,7 +26,6 @@ "label.default-date-range": "Диапазон дат по-умолчанию", "label.domain": "Домен", "label.enable-share-url": "Разрешить делиться ссылкой", - "label.event": "Event", "label.invalid": "Некорректный", "label.invalid-domain": "Некорректный домен", "label.last-days": "Последние {x} дней", @@ -35,7 +34,6 @@ "label.logout": "Выйти", "label.name": "Имя", "label.new-password": "Новый пароль", - "label.pageview": "Pageview", "label.password": "Пароль", "label.passwords-dont-match": "Пароли не совпадают", "label.profile": "Профиль", @@ -47,11 +45,8 @@ "label.this-year": "Этот год", "label.timezone": "Часовой пояс", "label.today": "Сегодня", - "label.type": "Type", "label.unknown": "Неизвестно", "label.username": "Имя пользователя", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Сайты", "message.active-users": "{x} текущих посетителей", "message.confirm-delete": "Вы уверены, что хотите удалить {target}?", diff --git a/lang/sv-SE.json b/lang/sv-SE.json index 35e61fc7..7153a449 100644 --- a/lang/sv-SE.json +++ b/lang/sv-SE.json @@ -26,7 +26,6 @@ "label.default-date-range": "Standard datum-urval", "label.domain": "Domän", "label.enable-share-url": "Aktivera delnings-URL", - "label.event": "Event", "label.invalid": "Ogiltig", "label.invalid-domain": "Ogiltig domän", "label.last-days": "Senaste {x} dagarna", @@ -35,7 +34,6 @@ "label.logout": "Logga ut", "label.name": "Namn", "label.new-password": "Nytt lösenord", - "label.pageview": "Pageview", "label.password": "Lösenord", "label.passwords-dont-match": "Lösenorden är inte samma", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Detta år", "label.timezone": "Tidszon", "label.today": "Idag", - "label.type": "Type", "label.unknown": "Okänd", "label.username": "Användarnamn", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Webbsajt", "message.active-users": "{x} {x, plural, one {besökare} other {besökare}} just nu", "message.confirm-delete": "Är du säker på att du vill radera {target}?", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index c16ba8e5..8448303a 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -26,7 +26,6 @@ "label.default-date-range": "Varsayılan tarih aralığı", "label.domain": "Alan adı", "label.enable-share-url": "Anonim paylaşım URL'i aktif", - "label.event": "Event", "label.invalid": "Geçeriz", "label.invalid-domain": "Geçersiz alan adı", "label.last-days": "Son {x} gün", @@ -35,7 +34,6 @@ "label.logout": "Çıkış Yap", "label.name": "İsim", "label.new-password": "Yeni parola", - "label.pageview": "Pageview", "label.password": "Parola", "label.passwords-dont-match": "Parolalar uyuşmuyor", "label.profile": "Profil", @@ -47,11 +45,8 @@ "label.this-year": "Bu yıl", "label.timezone": "Zaman dilimi", "label.today": "Bugün", - "label.type": "Type", "label.unknown": "Bilinmeyen", "label.username": "Kullanıcı adı", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Web siteleri", "message.active-users": "{x} aktif ziyaretçi", "message.confirm-delete": "{target} kaydını silmek istediğinizden emin misiniz?", diff --git a/lang/uk-UA.json b/lang/uk-UA.json index 7f92162e..ce87cf68 100644 --- a/lang/uk-UA.json +++ b/lang/uk-UA.json @@ -26,7 +26,6 @@ "label.default-date-range": "Діапазон дат за умовчанням", "label.domain": "Домен", "label.enable-share-url": "Дозволити ділитися посиланням", - "label.event": "Event", "label.invalid": "Некоректний", "label.invalid-domain": "Некоректний домен", "label.last-days": "Останні {x} днів", @@ -35,7 +34,6 @@ "label.logout": "Вийти", "label.name": "Ім'я", "label.new-password": "Новий пароль", - "label.pageview": "Pageview", "label.password": "Пароль", "label.passwords-dont-match": "Паролі не співпадають", "label.profile": "Профіль", @@ -47,11 +45,8 @@ "label.this-year": "Поточний рік", "label.timezone": "Часовий пояс", "label.today": "Сьогодні", - "label.type": "Type", "label.unknown": "Невідомо", "label.username": "Ім'я користувача", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "Веб-сайти", "message.active-users": "{x} поточних відвідувачів", "message.confirm-delete": "Ви впевнені, що бажаєте видалити {target}?", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 6f7150d9..5b5653c5 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -26,7 +26,6 @@ "label.default-date-range": "默认日期范围", "label.domain": "域名", "label.enable-share-url": "激活共享链接", - "label.event": "Event", "label.invalid": "输入无效", "label.invalid-domain": "无效域名", "label.last-days": "最近 {x} 天", @@ -35,7 +34,6 @@ "label.logout": "退出", "label.name": "名字", "label.new-password": "新密码", - "label.pageview": "Pageview", "label.password": "密码", "label.passwords-dont-match": "密码不一致", "label.profile": "个人资料", @@ -47,11 +45,8 @@ "label.this-year": "今年", "label.timezone": "时区", "label.today": "今天", - "label.type": "Type", "label.unknown": "未知", "label.username": "用户名", - "label.visitor": "Visitor", - "label.website": "Website", "label.websites": "网站", "message.active-users": "当前在线 {x} 人", "message.confirm-delete": "你确定要删除{target}吗?", diff --git a/lib/constants.js b/lib/constants.js index 95d78f58..721f5e1c 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -51,6 +51,7 @@ export const EVENT_COLORS = [ '#ffec16', ]; +export const DEFAUL_CHART_HEIGHT = 400; export const DEFAULT_ANIMATION_DURATION = 300; export const DEFAULT_DATE_RANGE = '24hour'; diff --git a/package.json b/package.json index 5d4d077c..6ff5b687 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.88.0", + "version": "0.89.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From 910481e6296c6c536cf878cc36fa4ef64fc8ff11 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 20:37:24 -0700 Subject: [PATCH 44/71] Added grid layout components. --- components/layout/GridLayout.js | 31 +++++++++++ components/layout/GridLayout.module.css | 40 ++++++++++++++ components/metrics/EventsChart.js | 3 +- components/metrics/RealtimeLog.js | 5 +- components/metrics/RealtimeLog.module.css | 9 ++- components/pages/RealtimeDashboard.js | 40 +++++++++++--- components/pages/WebsiteDetails.js | 64 +++++++++++----------- components/pages/WebsiteDetails.module.css | 31 +---------- styles/index.css | 3 + 9 files changed, 154 insertions(+), 72 deletions(-) create mode 100644 components/layout/GridLayout.js create mode 100644 components/layout/GridLayout.module.css diff --git a/components/layout/GridLayout.js b/components/layout/GridLayout.js new file mode 100644 index 00000000..01ec1e8f --- /dev/null +++ b/components/layout/GridLayout.js @@ -0,0 +1,31 @@ +import React from 'react'; +import classNames from 'classnames'; +import styles from './GridLayout.module.css'; + +export default function GridLayout({ className, children }) { + return
{children}
; +} + +export const GridRow = ({ className, children }) => { + return
{children}
; +}; + +export const GridColumn = ({ xs, sm, md, lg, xl, className, children }) => { + const classes = []; + + classes.push(xs ? `col-${xs}` : 'col'); + + if (sm) { + classes.push(`col-sm-${sm}`); + } + if (md) { + classes.push(`col-md-${md}`); + } + if (lg) { + classes.push(`col-lg-${lg}`); + } + if (xl) { + classes.push(`col-lg-${xl}`); + } + return
{children}
; +}; diff --git a/components/layout/GridLayout.module.css b/components/layout/GridLayout.module.css new file mode 100644 index 00000000..f17c195e --- /dev/null +++ b/components/layout/GridLayout.module.css @@ -0,0 +1,40 @@ +.grid { + display: flex; + flex-direction: column; +} + +.col { + display: flex; + flex-direction: column; +} + +.row { + border-top: 1px solid var(--gray300); + min-height: 430px; +} + +.row > .col { + border-left: 1px solid var(--gray300); + padding: 20px; +} + +.row > .col:first-child { + border-left: 0; + padding-left: 0; +} + +.row > .col:last-child { + padding-right: 0; +} + +@media only screen and (max-width: 992px) { + .row { + border: 0; + } + + .row > .col { + border-top: 1px solid var(--gray300); + border-left: 0; + padding: 0; + } +} diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js index c2804f9e..e401402d 100644 --- a/components/metrics/EventsChart.js +++ b/components/metrics/EventsChart.js @@ -8,7 +8,7 @@ import useTimezone from 'hooks/useTimezone'; import usePageQuery from 'hooks/usePageQuery'; import { EVENT_COLORS } from 'lib/constants'; -export default function EventsChart({ websiteId, token }) { +export default function EventsChart({ websiteId, className, token }) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate, unit, modified } = dateRange; const [timezone] = useTimezone(); @@ -79,6 +79,7 @@ export default function EventsChart({ websiteId, token }) { return ( +
+ +
- + {Row}
diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index af100708..5fa3d7ae 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -2,13 +2,20 @@ font-size: var(--font-size-xsmall); } +.header { + display: flex; + align-items: center; + justify-content: space-between; + line-height: 40px; + font-weight: 600; +} + .row { display: flex; border-bottom: 1px solid var(--gray300); } .body { - height: 600px; overflow: auto; } diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index fd123f8b..2b878f1a 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -1,12 +1,12 @@ import React, { useState, useEffect, useMemo } from 'react'; -import classNames from 'classnames'; import { subMinutes, startOfMinute } from 'date-fns'; import Page from 'components/layout/Page'; -import useFetch from 'hooks/useFetch'; +import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout'; import RealtimeChart from '../metrics/RealtimeChart'; import RealtimeLog from '../metrics/RealtimeLog'; import styles from './RealtimeDashboard.module.css'; import RealtimeHeader from '../metrics/RealtimeHeader'; +import useFetch from 'hooks/useFetch'; const REALTIME_RANGE = 30; const REALTIME_INTERVAL = 5000; @@ -36,10 +36,22 @@ export default function RealtimeDashboard() { const realtimeData = useMemo(() => { if (websiteId) { const { pageviews, sessions, events, ...props } = data; + const countries = sessions.reduce((obj, { country }) => { + if (country) { + if (!obj[country]) { + obj[country] = 1; + } else { + obj[country] += 1; + } + } + return obj; + }, {}); + return { pageviews: filterWebsite(pageviews, websiteId), sessions: filterWebsite(sessions, websiteId), events: filterWebsite(events, websiteId), + countries, ...props, }; } @@ -83,12 +95,24 @@ export default function RealtimeDashboard() { records={REALTIME_RANGE} />
-
-
- -
-
hi.
-
+ + + + + + + x + + + + + x + + + x + + + ); } diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 23b19f19..7f64b6f6 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -4,6 +4,7 @@ import classNames from 'classnames'; import WebsiteChart from 'components/metrics/WebsiteChart'; import WorldMap from 'components/common/WorldMap'; import Page from 'components/layout/Page'; +import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout'; import MenuLayout from 'components/layout/MenuLayout'; import Link from 'components/common/Link'; import Loading from 'components/common/Loading'; @@ -19,6 +20,7 @@ import EventsTable from '../metrics/EventsTable'; import EventsChart from '../metrics/EventsChart'; import useFetch from 'hooks/useFetch'; import usePageQuery from 'hooks/usePageQuery'; +import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; const views = { url: PagesTable, @@ -100,7 +102,7 @@ export default function WebsiteDetails({ websiteId, token }) { function handleDataLoad() { if (!chartLoaded) { - setTimeout(() => setChartLoaded(true), 300); + setTimeout(() => setChartLoaded(true), DEFAULT_ANIMATION_DURATION); } } @@ -124,45 +126,43 @@ export default function WebsiteDetails({ websiteId, token }) {
{!chartLoaded && } {chartLoaded && !view && ( - <> -
-
+ + + -
-
+ + -
-
-
-
+ + + + -
-
+ + -
-
+ + -
-
-
-
+ + + + -
-
+ + -
-
-
0 })} - > -
+ + + 0 })}> + -
-
- -
-
- + + + + + + )} {view && ( [class*='col-'] { - border-left: 1px solid var(--gray300); - padding: 20px; -} - -.row > [class*='col-']:first-child { - border-left: 0; - padding-left: 0; -} - -.row > [class*='col-']:last-child { - padding-right: 0; -} - .hidden { display: none; } -@media only screen and (max-width: 992px) { - .row { - border: 0; - } - - .row > [class*='col-'] { - border-top: 1px solid var(--gray300); - border-left: 0; - padding: 0; - } +.eventschart { + padding: 30px 0; } diff --git a/styles/index.css b/styles/index.css index c0e2ae6d..c695a2cf 100644 --- a/styles/index.css +++ b/styles/index.css @@ -37,6 +37,9 @@ h4, h5, h6 { font-weight: 400; + line-height: 30px; + padding: 0; + margin: 0; } button, From 51955c69ec4b143fc365a9cb740356fa8bf8ec80 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 9 Oct 2020 21:08:30 -0700 Subject: [PATCH 45/71] Added Finnish language. --- lib/lang.js | 5 +++++ package.json | 2 +- public/country/fi-FI.json | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 public/country/fi-FI.json diff --git a/lib/lang.js b/lib/lang.js index 200ac893..4b1841f5 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -17,6 +17,7 @@ import { nb, id, uk, + fi, } from 'date-fns/locale'; import enMessages from 'lang-compiled/en-US.json'; import nlMessages from 'lang-compiled/nl-NL.json'; @@ -37,6 +38,7 @@ import roMessages from 'lang-compiled/ro-RO.json'; import nbNOMessages from 'lang-compiled/nb-NO.json'; import idMessages from 'lang-compiled/id-ID.json'; import ukMessages from 'lang-compiled/uk-UA.json'; +import fiMessages from 'lang-compiled/fi-FI.json'; export const messages = { 'en-US': enMessages, @@ -58,6 +60,7 @@ export const messages = { 'nb-NO': nbNOMessages, 'id-ID': idMessages, 'uk-UA': ukMessages, + 'fi-FI': fiMessages, }; export const dateLocales = { @@ -80,6 +83,7 @@ export const dateLocales = { 'nb-NO': nb, 'id-ID': id, 'uk-UA': uk, + 'fi-FI': fi, }; export const menuOptions = [ @@ -99,6 +103,7 @@ export const menuOptions = [ { label: 'Português', value: 'pt-PT', display: 'pt' }, { label: 'Русский', value: 'ru-RU', display: 'ru' }, { label: 'Română', value: 'ro-RO', display: 'ro' }, + { label: 'Suomi', value: 'fi-FI', display: 'fi' }, { label: 'Svenska', value: 'sv-SE', display: 'sv' }, { label: 'Türkçe', value: 'tr-TR', display: 'tr' }, { label: 'українська', value: 'uk-UA', display: 'uk' }, diff --git a/package.json b/package.json index 6ff5b687..3e2c5c15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.89.0", + "version": "0.90.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", diff --git a/public/country/fi-FI.json b/public/country/fi-FI.json new file mode 100644 index 00000000..6410ee12 --- /dev/null +++ b/public/country/fi-FI.json @@ -0,0 +1 @@ +{"AF":"Afganistan","AX":"Ahvenanmaa","NL":"Alankomaat","AL":"Albania","DZ":"Algeria","AS":"Amerikan Samoa","AD":"Andorra","AO":"Angola","AI":"Anguilla","AQ":"Antarktis","AG":"Antigua ja Barbuda","AE":"Arabiemiirikunnat","AR":"Argentiina","AM":"Armenia","AW":"Aruba","AU":"Australia","AZ":"Azerbaid\u017ean","BS":"Bahama","BH":"Bahrain","BD":"Bangladesh","BB":"Barbados","BE":"Belgia","BZ":"Belize","BJ":"Benin","BM":"Bermuda","BT":"Bhutan","BO":"Bolivia","BA":"Bosnia ja Hertsegovina","BW":"Botswana","BV":"Bouvet\u2019nsaari","BR":"Brasilia","IO":"Brittil\u00e4inen Intian valtameren alue","VG":"Brittil\u00e4iset Neitsytsaaret","BN":"Brunei","BG":"Bulgaria","BF":"Burkina Faso","BI":"Burundi","KY":"Caymansaaret","CL":"Chile","CK":"Cookinsaaret","CR":"Costa Rica","CW":"Cura\u00e7ao","DJ":"Djibouti","DM":"Dominica","DO":"Dominikaaninen tasavalta","EC":"Ecuador","EG":"Egypti","SV":"El Salvador","ER":"Eritrea","ES":"Espanja","ZA":"Etel\u00e4-Afrikka","GS":"Etel\u00e4-Georgia ja Etel\u00e4iset Sandwichsaaret","KR":"Etel\u00e4-Korea","SS":"Etel\u00e4-Sudan","ET":"Etiopia","FK":"Falklandinsaaret","FJ":"Fid\u017ei","PH":"Filippiinit","FO":"F\u00e4rsaaret","GA":"Gabon","GM":"Gambia","GE":"Georgia","GH":"Ghana","GI":"Gibraltar","GD":"Grenada","GL":"Gr\u00f6nlanti","GP":"Guadeloupe","GU":"Guam","GT":"Guatemala","GG":"Guernsey","GN":"Guinea","GW":"Guinea-Bissau","GY":"Guyana","HT":"Haiti","HM":"Heard ja McDonaldinsaaret","HN":"Honduras","HK":"Hongkong \u2013 Kiinan e.h.a.","SJ":"Huippuvuoret ja Jan Mayen","ID":"Indonesia","IN":"Intia","IQ":"Irak","IR":"Iran","IE":"Irlanti","IS":"Islanti","GB":"Iso-Britannia","IL":"Israel","IT":"Italia","TL":"It\u00e4-Timor","AT":"It\u00e4valta","JM":"Jamaika","JP":"Japani","YE":"Jemen","JE":"Jersey","JO":"Jordania","CX":"Joulusaari","KH":"Kambod\u017ea","CM":"Kamerun","CA":"Kanada","CV":"Kap Verde","BQ":"Karibian Alankomaat","KZ":"Kazakstan","KE":"Kenia","CF":"Keski-Afrikan tasavalta","CN":"Kiina","KG":"Kirgisia","KI":"Kiribati","CO":"Kolumbia","KM":"Komorit","CD":"Kongon demokraattinen tasavalta","CG":"Kongon tasavalta","CC":"Kookossaaret (Keelingsaaret)","GR":"Kreikka","HR":"Kroatia","CU":"Kuuba","KW":"Kuwait","CY":"Kypros","LA":"Laos","LV":"Latvia","LS":"Lesotho","LB":"Libanon","LR":"Liberia","LY":"Libya","LI":"Liechtenstein","LT":"Liettua","LU":"Luxemburg","EH":"L\u00e4nsi-Sahara","MO":"Macao \u2013 Kiinan e.h.a.","MG":"Madagaskar","MW":"Malawi","MV":"Malediivit","MY":"Malesia","ML":"Mali","MT":"Malta","IM":"Mansaari","MA":"Marokko","MH":"Marshallinsaaret","MQ":"Martinique","MR":"Mauritania","MU":"Mauritius","YT":"Mayotte","MX":"Meksiko","FM":"Mikronesian liittovaltio","MD":"Moldova","MC":"Monaco","MN":"Mongolia","ME":"Montenegro","MS":"Montserrat","MZ":"Mosambik","MM":"Myanmar (Burma)","NA":"Namibia","NR":"Nauru","NP":"Nepal","NI":"Nicaragua","NE":"Niger","NG":"Nigeria","NU":"Niue","NF":"Norfolkinsaari","NO":"Norja","CI":"Norsunluurannikko","OM":"Oman","PK":"Pakistan","PW":"Palau","PS":"Palestiinalaisalueet","PA":"Panama","PG":"Papua-Uusi-Guinea","PY":"Paraguay","PE":"Peru","PN":"Pitcairn","KP":"Pohjois-Korea","MK":"Pohjois-Makedonia","MP":"Pohjois-Mariaanit","PT":"Portugali","PR":"Puerto Rico","PL":"Puola","GQ":"P\u00e4iv\u00e4ntasaajan Guinea","QA":"Qatar","FR":"Ranska","TF":"Ranskan etel\u00e4iset alueet","GF":"Ranskan Guayana","PF":"Ranskan Polynesia","RE":"R\u00e9union","RO":"Romania","RW":"Ruanda","SE":"Ruotsi","SH":"Saint Helena","KN":"Saint Kitts ja Nevis","LC":"Saint Lucia","VC":"Saint Vincent ja Grenadiinit","BL":"Saint-Barth\u00e9lemy","MF":"Saint-Martin","PM":"Saint-Pierre ja Miquelon","DE":"Saksa","SB":"Salomonsaaret","ZM":"Sambia","WS":"Samoa","SM":"San Marino","ST":"S\u00e3o Tom\u00e9 ja Pr\u00edncipe","SA":"Saudi-Arabia","SN":"Senegal","RS":"Serbia","SC":"Seychellit","SL":"Sierra Leone","SG":"Singapore","SX":"Sint Maarten","SK":"Slovakia","SI":"Slovenia","SO":"Somalia","LK":"Sri Lanka","SD":"Sudan","FI":"Suomi","SR":"Suriname","CH":"Sveitsi","SZ":"Swazimaa","SY":"Syyria","TJ":"Tad\u017eikistan","TW":"Taiwan","TZ":"Tansania","DK":"Tanska","TH":"Thaimaa","TG":"Togo","TK":"Tokelau","TO":"Tonga","TT":"Trinidad ja Tobago","TD":"T\u0161ad","CZ":"T\u0161ekki","TN":"Tunisia","TR":"Turkki","TM":"Turkmenistan","TC":"Turks- ja Caicossaaret","TV":"Tuvalu","UG":"Uganda","UA":"Ukraina","HU":"Unkari","UY":"Uruguay","NC":"Uusi-Kaledonia","NZ":"Uusi-Seelanti","UZ":"Uzbekistan","BY":"Valko-Ven\u00e4j\u00e4","VU":"Vanuatu","VA":"Vatikaani","VE":"Venezuela","RU":"Ven\u00e4j\u00e4","VN":"Vietnam","EE":"Viro","WF":"Wallis ja Futuna","US":"Yhdysvallat","UM":"Yhdysvaltain erillissaaret","VI":"Yhdysvaltain Neitsytsaaret","ZW":"Zimbabwe"} \ No newline at end of file From 8912daa2fad940ec531d19ddbd13d1cba046ee32 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 10 Oct 2020 00:07:08 -0700 Subject: [PATCH 46/71] Refactored data tables. Added realtime tables. --- components/layout/GridLayout.js | 2 +- components/metrics/DataTable.js | 91 +++++++++++++++ components/metrics/DataTable.module.css | 96 ++++++++++++++++ components/metrics/MetricsBar.js | 2 - components/metrics/MetricsTable.js | 118 +++++-------------- components/metrics/MetricsTable.module.css | 89 --------------- components/metrics/RealtimeHeader.js | 14 +-- components/pages/RealtimeDashboard.js | 127 ++++++++++++++++----- lang/da-DK.json | 1 + lang/de-DE.json | 1 + lang/el-GR.json | 1 + lang/en-US.json | 1 + lang/es-MX.json | 1 + lang/fi-FI.json | 1 + lang/fo-FO.json | 1 + lang/fr-FR.json | 1 + lang/id-ID.json | 1 + lang/ja-JP.json | 1 + lang/mn-MN.json | 1 + lang/nb-NO.json | 1 + lang/nl-NL.json | 1 + lang/pt-PT.json | 1 + lang/ro-RO.json | 1 + lang/ru-RU.json | 1 + lang/sv-SE.json | 1 + lang/tr-TR.json | 1 + lang/uk-UA.json | 1 + lang/zh-CN.json | 1 + package.json | 2 +- 29 files changed, 335 insertions(+), 226 deletions(-) create mode 100644 components/metrics/DataTable.js create mode 100644 components/metrics/DataTable.module.css diff --git a/components/layout/GridLayout.js b/components/layout/GridLayout.js index 01ec1e8f..1d3170d6 100644 --- a/components/layout/GridLayout.js +++ b/components/layout/GridLayout.js @@ -13,7 +13,7 @@ export const GridRow = ({ className, children }) => { export const GridColumn = ({ xs, sm, md, lg, xl, className, children }) => { const classes = []; - classes.push(xs ? `col-${xs}` : 'col'); + classes.push(xs ? `col-${xs}` : 'col-12'); if (sm) { classes.push(`col-sm-${sm}`); diff --git a/components/metrics/DataTable.js b/components/metrics/DataTable.js new file mode 100644 index 00000000..9e12d887 --- /dev/null +++ b/components/metrics/DataTable.js @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { FixedSizeList } from 'react-window'; +import { useSpring, animated, config } from 'react-spring'; +import classNames from 'classnames'; +import NoData from 'components/common/NoData'; +import { formatNumber, formatLongNumber } from 'lib/format'; +import styles from './DataTable.module.css'; + +export default function DataTable({ + data, + title, + metric, + className, + limit, + renderLabel, + height = 400, + animate = true, +}) { + const [format, setFormat] = useState(true); + const formatFunc = format ? formatLongNumber : formatNumber; + + const handleSetFormat = () => setFormat(state => !state); + + const getRow = row => { + const { x: label, y: value, z: percent } = row; + + return ( + + ); + }; + + const Row = ({ index, style }) => { + return
{getRow(data[index])}
; + }; + + return ( +
+
+
{title}
+
+ {metric} +
+
+
+ {data?.length === 0 && } + {limit + ? data.map(row => getRow(row)) + : data.length > 0 && ( + + {Row} + + )} +
+
+ ); +} + +const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => { + const props = useSpring({ + width: percent, + y: value, + from: { width: 0, y: 0 }, + config: animate ? config.default : { duration: 0 }, + }); + + return ( +
+
{label}
+
+ {props.y?.interpolate(format)} +
+
+ `${n}%`) }} + /> + + {props.width.interpolate(n => `${n.toFixed(0)}%`)} + +
+
+ ); +}; diff --git a/components/metrics/DataTable.module.css b/components/metrics/DataTable.module.css new file mode 100644 index 00000000..b276e361 --- /dev/null +++ b/components/metrics/DataTable.module.css @@ -0,0 +1,96 @@ +.table { + position: relative; + font-size: var(--font-size-small); + display: flex; + flex-direction: column; + flex: 1; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + line-height: 40px; +} + +.title { + display: flex; + font-weight: 600; + font-size: var(--font-size-normal); +} + +.metric { + font-size: var(--font-size-small); + font-weight: 600; + text-align: center; + width: 100px; + cursor: pointer; +} + +.row { + position: relative; + height: 30px; + line-height: 30px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; + overflow: hidden; +} + +.label { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + flex: 2; +} + +.label a { + color: inherit; + text-decoration: none; +} + +.label a:hover { + color: var(--primary400); +} + +.label:empty { + color: #b3b3b3; +} + +.label:empty:before { + content: 'Unknown'; +} + +.value { + width: 50px; + text-align: right; + margin-right: 10px; + font-weight: 600; + cursor: pointer; +} + +.percent { + position: relative; + width: 50px; + color: var(--gray600); + border-left: 1px solid var(--gray600); + padding-left: 10px; + z-index: 1; +} + +.bar { + position: absolute; + top: 0; + left: 0; + height: 30px; + opacity: 0.1; + background: var(--primary400); + z-index: -1; +} + +.body { + position: relative; + flex: 1; + overflow: hidden; +} diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index 9fbc3054..3b159327 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -18,8 +18,6 @@ export default function MetricsBar({ websiteId, token, className }) { query: { url }, } = usePageQuery(); - console.log({ modified }); - const { data, error, loading } = useFetch( `/api/website/${websiteId}/stats`, { diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 2f508295..d4b246b3 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -1,19 +1,17 @@ -import React, { useState, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; -import { FixedSizeList } from 'react-window'; -import { useSpring, animated, config } from 'react-spring'; import classNames from 'classnames'; import Link from 'components/common/Link'; import Loading from 'components/common/Loading'; -import NoData from 'components/common/NoData'; import useFetch from 'hooks/useFetch'; import Arrow from 'assets/arrow-right.svg'; import { percentFilter } from 'lib/filters'; -import { formatNumber, formatLongNumber } from 'lib/format'; import useDateRange from 'hooks/useDateRange'; import usePageQuery from 'hooks/usePageQuery'; +import ErrorMessage from 'components/common/ErrorMessage'; +import DataTable from './DataTable'; +import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import styles from './MetricsTable.module.css'; -import ErrorMessage from '../common/ErrorMessage'; export default function MetricsTable({ websiteId, @@ -49,15 +47,12 @@ export default function MetricsTable({ token, }, onDataLoad, - delay: 300, + delay: DEFAULT_ANIMATION_DURATION, }, [modified], ); - const [format, setFormat] = useState(true); - const formatFunc = format ? formatLongNumber : formatNumber; - const shouldAnimate = limit > 0; - const rankings = useMemo(() => { + const filteredData = useMemo(() => { if (data) { const items = percentFilter(dataFilter ? dataFilter(data, filterOptions) : data); if (limit) { @@ -68,91 +63,34 @@ export default function MetricsTable({ return []; }, [data, error, dataFilter, filterOptions]); - const handleSetFormat = () => setFormat(state => !state); - - const getRow = row => { - const { x: label, y: value, z: percent } = row; - return ( - - ); - }; - - const Row = ({ index, style }) => { - return
{getRow(rankings[index])}
; - }; - return (
{!data && loading && } {error && } {data && !error && ( - <> -
-
{title}
-
- {metric} -
-
-
- {rankings?.length === 0 && } - {limit - ? rankings.map(row => getRow(row)) - : rankings.length > 0 && ( - - {Row} - - )} -
-
- {limit && ( - } - href={router.pathname} - as={resolve({ view: type })} - size="small" - iconRight - > - - - )} -
- + 0} + /> )} +
+ {limit && ( + } + href={router.pathname} + as={resolve({ view: type })} + size="small" + iconRight + > + + + )} +
); } - -const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => { - const props = useSpring({ - width: percent, - y: value, - from: { width: 0, y: 0 }, - config: animate ? config.default : { duration: 0 }, - }); - - return ( -
-
{label}
-
- {props.y?.interpolate(format)} -
-
- `${n}%`) }} - /> - - {props.width.interpolate(n => `${n.toFixed(0)}%`)} - -
-
- ); -}; diff --git a/components/metrics/MetricsTable.module.css b/components/metrics/MetricsTable.module.css index bbba0009..e93f536e 100644 --- a/components/metrics/MetricsTable.module.css +++ b/components/metrics/MetricsTable.module.css @@ -6,95 +6,6 @@ flex-direction: column; } -.header { - display: flex; - align-items: center; - justify-content: space-between; - line-height: 40px; -} - -.title { - display: flex; - font-weight: 600; - font-size: var(--font-size-normal); -} - -.metric { - font-size: var(--font-size-small); - font-weight: 600; - text-align: center; - width: 100px; - cursor: pointer; -} - -.row { - position: relative; - height: 30px; - line-height: 30px; - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 5px; - overflow: hidden; -} - -.label { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - flex: 2; -} - -.label a { - color: inherit; - text-decoration: none; -} - -.label a:hover { - color: var(--primary400); -} - -.label:empty { - color: #b3b3b3; -} - -.label:empty:before { - content: 'Unknown'; -} - -.value { - width: 50px; - text-align: right; - margin-right: 10px; - font-weight: 600; - cursor: pointer; -} - -.percent { - position: relative; - width: 50px; - color: var(--gray600); - border-left: 1px solid var(--gray600); - padding-left: 10px; - z-index: 1; -} - -.bar { - position: absolute; - top: 0; - left: 0; - height: 30px; - opacity: 0.1; - background: var(--primary400); - z-index: -1; -} - -.body { - position: relative; - flex: 1; - overflow: hidden; -} - .footer { display: flex; justify-content: center; diff --git a/components/metrics/RealtimeHeader.js b/components/metrics/RealtimeHeader.js index 09f83ff6..deea9387 100644 --- a/components/metrics/RealtimeHeader.js +++ b/components/metrics/RealtimeHeader.js @@ -10,17 +10,7 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect }) { label: , value: 0 }, ].concat(websites.map(({ name, website_id }) => ({ label: name, value: website_id }))); - const { pageviews, sessions, events } = data; - const countries = sessions.reduce((obj, { country }) => { - if (country) { - if (!obj[country]) { - obj[country] = 1; - } else { - obj[country] += 1; - } - } - return obj; - }, {}); + const { pageviews, sessions, events, countries } = data; return ( <> @@ -45,7 +35,7 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect }) /> } - value={Object.keys(countries).length} + value={countries.length} />
diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 2b878f1a..5c8f052a 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -1,5 +1,7 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { subMinutes, startOfMinute } from 'date-fns'; +import firstBy from 'thenby'; +import { percentFilter } from 'lib/filters'; import Page from 'components/layout/Page'; import GridLayout, { GridRow, GridColumn } from 'components/layout/GridLayout'; import RealtimeChart from '../metrics/RealtimeChart'; @@ -7,6 +9,11 @@ import RealtimeLog from '../metrics/RealtimeLog'; import styles from './RealtimeDashboard.module.css'; import RealtimeHeader from '../metrics/RealtimeHeader'; import useFetch from 'hooks/useFetch'; +import WorldMap from '../common/WorldMap'; +import DataTable from '../metrics/DataTable'; +import useLocale from 'hooks/useLocale'; +import useCountryNames from 'hooks/useCountryNames'; +import { FormattedMessage } from 'react-intl'; const REALTIME_RANGE = 30; const REALTIME_INTERVAL = 5000; @@ -23,6 +30,8 @@ function filterWebsite(data, id) { } export default function RealtimeDashboard() { + const [locale] = useLocale(); + const countryNames = useCountryNames(locale); const [data, setData] = useState(); const [websiteId, setWebsiteId] = useState(0); const { data: init, loading } = useFetch('/api/realtime', { params: { type: 'init' } }); @@ -33,41 +42,84 @@ export default function RealtimeDashboard() { headers: { 'x-umami-token': init?.token }, }); - const realtimeData = useMemo(() => { - if (websiteId) { - const { pageviews, sessions, events, ...props } = data; - const countries = sessions.reduce((obj, { country }) => { - if (country) { - if (!obj[country]) { - obj[country] = 1; - } else { - obj[country] += 1; - } - } - return obj; - }, {}); + const renderCountryName = useCallback(({ x }) => countryNames[x], []); - return { - pageviews: filterWebsite(pageviews, websiteId), - sessions: filterWebsite(sessions, websiteId), - events: filterWebsite(events, websiteId), - countries, - ...props, - }; + const realtimeData = useMemo(() => { + if (data) { + const { pageviews, sessions, events } = data; + + if (websiteId) { + return { + pageviews: filterWebsite(pageviews, websiteId), + sessions: filterWebsite(sessions, websiteId), + events: filterWebsite(events, websiteId), + }; + } } + return data; }, [data, websiteId]); + const countries = useMemo(() => { + if (realtimeData?.sessions) { + return percentFilter( + realtimeData.sessions + .reduce((arr, { country }) => { + if (country) { + const row = arr.find(({ x }) => x === country); + + if (!row) { + arr.push({ x: country, y: 1 }); + } else { + row.y += 1; + } + } + return arr; + }, []) + .sort(firstBy('y', -1)), + ); + } + return []; + }, [realtimeData]); + + const referrers = useMemo(() => { + if (realtimeData?.pageviews) { + return percentFilter( + realtimeData.pageviews + .reduce((arr, { referrer }) => { + if (referrer?.startsWith('http')) { + const { hostname } = new URL(referrer); + const row = arr.find(({ x }) => x === hostname); + + if (!row) { + arr.push({ x: hostname, y: 1 }); + } else { + row.y += 1; + } + } + return arr; + }, []) + .sort(firstBy('y', -1)), + ); + } + return []; + }, [realtimeData]); + useEffect(() => { if (init && !data) { - setData(init.data); + const { websites, data } = init; + const domains = init.websites.map(({ domain }) => domain); + + setData({ websites, domains, ...data }); } else if (updates) { const { pageviews, sessions, events, timestamp } = updates; - const minTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); + const time = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); + setData(state => ({ - pageviews: mergeData(state.pageviews, pageviews, minTime), - sessions: mergeData(state.sessions, sessions, minTime), - events: mergeData(state.events, events, minTime), + ...state, + pageviews: mergeData(state.pageviews, pageviews, time), + sessions: mergeData(state.sessions, sessions, time), + events: mergeData(state.events, events, time), timestamp, })); } @@ -77,14 +129,16 @@ export default function RealtimeDashboard() { return null; } - const { websites } = init; + const { websites } = data; + + //console.log({realtimeData, countries}); return (
@@ -101,15 +155,26 @@ export default function RealtimeDashboard() { - x + } + metric={} + data={referrers} + animate={false} + /> - x + } + metric={} + data={countries} + renderLabel={renderCountryName} + animate={false} + /> - x + diff --git a/lang/da-DK.json b/lang/da-DK.json index 5532bf69..b66b513d 100644 --- a/lang/da-DK.json +++ b/lang/da-DK.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Adgangskoder matcher ikke", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 df5e6dd1..4447f98b 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Passwörter stimmen nicht überein", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Erforderlich", "label.settings": "Einstellungen", "label.this-month": "Diesen Monat", diff --git a/lang/el-GR.json b/lang/el-GR.json index 7fbdbfbf..978de681 100644 --- a/lang/el-GR.json +++ b/lang/el-GR.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν", "label.profile": "Προφίλ", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Απαιτείται", "label.settings": "Ρυθμίσεις", "label.this-month": "Αυτο το μήνα", diff --git a/lang/en-US.json b/lang/en-US.json index 07670232..12231157 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Passwords don't match", "label.profile": "Profile", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Required", "label.settings": "Settings", "label.this-month": "This month", diff --git a/lang/es-MX.json b/lang/es-MX.json index 61395fcf..eaf92db6 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Las contraseñas no coinciden", "label.profile": "Perfil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Requerido", "label.settings": "Configuraciones", "label.this-month": "Este mes", diff --git a/lang/fi-FI.json b/lang/fi-FI.json index 9f0c930b..fa808980 100644 --- a/lang/fi-FI.json +++ b/lang/fi-FI.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Salasanat eivät täsmää", "label.profile": "Profiili", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Vaaditaan", "label.settings": "Asetukset", "label.this-month": "Tämä kuukausi", diff --git a/lang/fo-FO.json b/lang/fo-FO.json index ee5ab8cb..8cedbcdb 100644 --- a/lang/fo-FO.json +++ b/lang/fo-FO.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Loyniorðini eru ikki eins", "label.profile": "Brúkari", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 cb2cc103..ff00653d 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Les mots de passe ne correspondent pas", "label.profile": "Profile", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 7c7db298..9c05cf80 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Kata sandi tidak cocok", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Wajib", "label.settings": "Pengaturan", "label.this-month": "Bulan ini", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index 09458224..3a019f27 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "パスワードが一致しません", "label.profile": "プロファイル", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "必須", "label.settings": "設定", "label.this-month": "今月", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index 5e92c9c7..6a9a0c0b 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Нууц үг тохирохгүй байна", "label.profile": "Бүртгэл", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Шаардлагатай", "label.settings": "Тохиргоо", "label.this-month": "Энэ сар", diff --git a/lang/nb-NO.json b/lang/nb-NO.json index 0af11471..0988f361 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Passordene er ikke like", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 4613ed33..f36289cc 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Wachtwoorden komen niet overeen", "label.profile": "Profiel", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Verplicht", "label.settings": "Instellingen", "label.this-month": "Deze maand", diff --git a/lang/pt-PT.json b/lang/pt-PT.json index 81e3a334..d1f7ee3c 100644 --- a/lang/pt-PT.json +++ b/lang/pt-PT.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Palavra-passes não correspondem", "label.profile": "Perfil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 7e2f4800..3fe2fb92 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Parolele nu se potrivesc", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 8a1ffb1d..4b0ff88b 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Пароли не совпадают", "label.profile": "Профиль", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Обязательное", "label.settings": "Настройки", "label.this-month": "Этот месяц", diff --git a/lang/sv-SE.json b/lang/sv-SE.json index 7153a449..6133383b 100644 --- a/lang/sv-SE.json +++ b/lang/sv-SE.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Lösenorden är inte samma", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 8448303a..1aff9d2e 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Parolalar uyuşmuyor", "label.profile": "Profil", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "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 ce87cf68..27d04279 100644 --- a/lang/uk-UA.json +++ b/lang/uk-UA.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "Паролі не співпадають", "label.profile": "Профіль", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "Обов'язкове", "label.settings": "Налаштування", "label.this-month": "Поточний місяць", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 5b5653c5..18a82ac4 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -38,6 +38,7 @@ "label.passwords-dont-match": "密码不一致", "label.profile": "个人资料", "label.realtime": "Realtime", + "label.realtime-logs": "Realtime logs", "label.required": "必填", "label.settings": "设置", "label.this-month": "本月", diff --git a/package.json b/package.json index 3e2c5c15..1398c724 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.90.0", + "version": "0.91.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", From a039f405b6f8de2be10c714fbbed7f4d67076ec6 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 10 Oct 2020 01:16:28 -0700 Subject: [PATCH 47/71] Refactored realtime components. --- components/metrics/DataTable.module.css | 11 +++-- components/metrics/MetricsTable.js | 2 + components/metrics/RealtimeLog.js | 49 ++++++----------------- components/metrics/RealtimeLog.module.css | 13 +++++- components/pages/RealtimeDashboard.js | 10 +++-- components/pages/WebsiteDetails.js | 2 +- package.json | 3 +- yarn.lock | 12 ++++++ 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/components/metrics/DataTable.module.css b/components/metrics/DataTable.module.css index b276e361..a1dc61fc 100644 --- a/components/metrics/DataTable.module.css +++ b/components/metrics/DataTable.module.css @@ -6,6 +6,11 @@ flex: 1; } +.body { + position: relative; + flex: 1; +} + .header { display: flex; align-items: center; @@ -88,9 +93,3 @@ background: var(--primary400); z-index: -1; } - -.body { - position: relative; - flex: 1; - overflow: hidden; -} diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index d4b246b3..19cd5b95 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -25,6 +25,7 @@ export default function MetricsTable({ filterOptions, limit, renderLabel, + height, onDataLoad = () => {}, }) { const [dateRange] = useDateRange(websiteId); @@ -75,6 +76,7 @@ export default function MetricsTable({ className={className} renderLabel={renderLabel} limit={limit} + height={height} animate={limit > 0} /> )} diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 9bd36d91..426bd3ce 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -1,11 +1,9 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { FixedSizeList } from 'react-window'; -import classNames from 'classnames'; import firstBy from 'thenby'; import { format } from 'date-fns'; import Icon from 'components/common/Icon'; -import Table, { TableRow } from 'components/common/Table'; import Tag from 'components/common/Tag'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; @@ -33,29 +31,6 @@ export default function RealtimeLog({ data, websites }) { return [...pageviews, ...sessions, ...events].sort(firstBy('created_at', -1)); }, [data]); - const columns = [ - { - key: 'time', - className: classNames(styles.time, 'col-3 col-lg-1'), - render: ({ created_at }) => format(new Date(created_at), 'h:mm:ss'), - }, - { - key: 'website', - className: classNames(styles.website, 'col-9 col-lg-2'), - render: getWebsite, - }, - { - key: 'detail', - className: classNames(styles.detail, 'col-12 col-lg-9'), - render: row => ( - <> - - {getDetail(row)} - - ), - }, - ]; - function getType({ view_id, session_id, event_id }) { if (event_id) { return TYPE_EVENT; @@ -110,29 +85,29 @@ export default function RealtimeLog({ data, websites }) { } const Row = ({ index, style }) => { + const row = logs[index]; return ( -
- +
+
{format(new Date(row.created_at), 'h:mm:ss')}
+
{getWebsite(row)}
+
+ + {getDetail(row)} +
); }; return ( -
+
- - +
+ {Row} -
+
); } diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index 5fa3d7ae..803cfe0d 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -6,12 +6,15 @@ display: flex; align-items: center; justify-content: space-between; + font-size: 16px; line-height: 40px; font-weight: 600; } .row { display: flex; + align-items: center; + height: 40px; border-bottom: 1px solid var(--gray300); } @@ -20,17 +23,23 @@ } .icon { - align-self: center; - margin-right: 20px; + margin-right: 10px; +} + +.time { + min-width: 60px; + overflow: hidden; } .website { + min-width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .detail { + display: flex; flex: 1; } diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 5c8f052a..30bf1ee4 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -16,7 +16,7 @@ import useCountryNames from 'hooks/useCountryNames'; import { FormattedMessage } from 'react-intl'; const REALTIME_RANGE = 30; -const REALTIME_INTERVAL = 5000; +const REALTIME_INTERVAL = 55000; function mergeData(state, data, time) { const ids = state.map(({ __id }) => __id); @@ -151,17 +151,18 @@ export default function RealtimeDashboard() {
- - - } metric={} data={referrers} + height={400} animate={false} /> + + + @@ -170,6 +171,7 @@ export default function RealtimeDashboard() { metric={} data={countries} renderLabel={renderCountryName} + height={500} animate={false} /> diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 7f64b6f6..39b634e5 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -171,7 +171,7 @@ export default function WebsiteDetails({ websiteId, token }) { contentClassName={styles.content} menu={menuOptions} > - + )} diff --git a/package.json b/package.json index 1398c724..4347eaa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.91.0", + "version": "0.92.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -83,6 +83,7 @@ "react-simple-maps": "^2.1.2", "react-spring": "^8.0.27", "react-tooltip": "^4.2.10", + "react-use-measure": "^2.0.2", "react-window": "^1.8.5", "redux": "^4.0.5", "redux-thunk": "^2.3.0", diff --git a/yarn.lock b/yarn.lock index df03a2ae..b8d1e81c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3228,6 +3228,11 @@ date-fns@^2.16.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== +debounce@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" + integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== + debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" @@ -7337,6 +7342,13 @@ react-tooltip@^4.2.10: prop-types "^15.7.2" uuid "^7.0.3" +react-use-measure@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.0.2.tgz#06b4f99b887d5dfcd7b7167a2da063d97ec8f62f" + integrity sha512-/+eSmQiU2ePNTwFCXX4JPrQNMvyu3sWrSDi/n5F6IMXwboB46IvtU8VHvG7Nc+egvtM7sBJKwmUx/vx6KIRDog== + dependencies: + debounce "^1.2.0" + react-window@^1.8.5: version "1.8.5" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1" From f2cfab5078d857e12b2d625c8eebf94a1732a4c2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 10 Oct 2020 01:28:26 -0700 Subject: [PATCH 48/71] Updated realtime logs component. --- components/metrics/RealtimeLog.js | 2 +- components/metrics/RealtimeLog.module.css | 7 +++---- lang/da-DK.json | 2 +- lang/de-DE.json | 2 +- lang/el-GR.json | 2 +- lang/en-US.json | 2 +- lang/es-MX.json | 2 +- lang/fo-FO.json | 2 +- lang/fr-FR.json | 2 +- lang/id-ID.json | 2 +- lang/ja-JP.json | 2 +- lang/mn-MN.json | 2 +- lang/nb-NO.json | 2 +- lang/nl-NL.json | 2 +- lang/pt-PT.json | 2 +- lang/ro-RO.json | 2 +- lang/ru-RU.json | 2 +- lang/sv-SE.json | 2 +- lang/tr-TR.json | 2 +- lang/uk-UA.json | 2 +- lang/zh-CN.json | 2 +- 21 files changed, 23 insertions(+), 24 deletions(-) diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 426bd3ce..80c14e37 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -89,11 +89,11 @@ export default function RealtimeLog({ data, websites }) { return (
{format(new Date(row.created_at), 'h:mm:ss')}
-
{getWebsite(row)}
{getDetail(row)}
+
{getWebsite(row)}
); }; diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index 803cfe0d..5924b331 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -1,5 +1,6 @@ .table { font-size: var(--font-size-xsmall); + overflow: hidden; } .header { @@ -32,10 +33,8 @@ } .website { - min-width: 120px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; + text-align: right; + padding-right: 20px; } .detail { diff --git a/lang/da-DK.json b/lang/da-DK.json index b66b513d..7f476ef7 100644 --- a/lang/da-DK.json +++ b/lang/da-DK.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Få sporingskode", "message.go-to-settings": "Gå til betjeningspanel", "message.incorrect-username-password": "Ugyldigt brugernavn/adgangskode.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/de-DE.json b/lang/de-DE.json index 4447f98b..ccba1300 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Erstelle Tracking Kennung", "message.go-to-settings": "Zu den Einstellungen", "message.incorrect-username-password": "Falsches Passwort oder Benutzername.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Eine neue Version umami {version} ist verfügbar!", "message.no-data-available": "Keine Daten vorhanden.", "message.no-websites-configured": "Es ist keine Webseite vorhanden.", diff --git a/lang/el-GR.json b/lang/el-GR.json index 978de681..fd091a94 100644 --- a/lang/el-GR.json +++ b/lang/el-GR.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Λήψη κώδικα παρακολούθησης", "message.go-to-settings": "Μεταβείτε στις ρυθμίσεις", "message.incorrect-username-password": "Εσφαλμένο όνομα χρήστη / κωδικός πρόσβασης.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "A new version of umami {version} is available!", "message.no-data-available": "Δεν υπάρχουν διαθέσιμα δεδομένα.", "message.no-websites-configured": "Δεν έχετε ρυθμίσει κανένα ιστότοπο.", diff --git a/lang/en-US.json b/lang/en-US.json index 12231157..7bf0ee99 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Get tracking code", "message.go-to-settings": "Go to settings", "message.incorrect-username-password": "Incorrect username/password.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/es-MX.json b/lang/es-MX.json index eaf92db6..230d3e09 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -58,7 +58,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/fo-FO.json b/lang/fo-FO.json index 8cedbcdb..256382b0 100644 --- a/lang/fo-FO.json +++ b/lang/fo-FO.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Fá sporings kotu", "message.go-to-settings": "Far til stillingar", "message.incorrect-username-password": "Skeivt brúkaranavn/loyniorð.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index ff00653d..49cdacce 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -58,7 +58,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/id-ID.json b/lang/id-ID.json index 9c05cf80..1cf3a312 100644 --- a/lang/id-ID.json +++ b/lang/id-ID.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Dapatkan kode pelacakan", "message.go-to-settings": "Pergi ke pengaturan", "message.incorrect-username-password": "Nama pengguna/kata sandi salah.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Versi terbaru umami {version} telah tersedia!", "message.no-data-available": "Tidak ada data.", "message.no-websites-configured": "Anda tidak memiliki situs web yang dikonfigurasi.", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index 3a019f27..60fa91f1 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "トラッキングコードを取得", "message.go-to-settings": "設定する", "message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "新しいバージョン({version})が利用可能です!", "message.no-data-available": "データがありません。", "message.no-websites-configured": "Webサイトが設定されていません。", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index 6a9a0c0b..0e494938 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Мөрдөх код авах", "message.go-to-settings": "Тохиргоо руу очих", "message.incorrect-username-password": "Буруу хэрэглэгчийн нэр/нууц үг.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Umami-гийн шинэ хувилбар {version} гарсан байна!", "message.no-data-available": "Өгөгдөл алга.", "message.no-websites-configured": "Та ямар нэгэн веб тохируулаагүй байна.", diff --git a/lang/nb-NO.json b/lang/nb-NO.json index 0988f361..c04f6939 100644 --- a/lang/nb-NO.json +++ b/lang/nb-NO.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Få sporingskode", "message.go-to-settings": "Gå til innstillinger", "message.incorrect-username-password": "Ugyldig brukernavn/passord.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "En ny versjon av umami {version} er tilgjengelig!", "message.no-data-available": "Ingen data tilgjengelig.", "message.no-websites-configured": "Du har ikke satt opp noen nettsteder.", diff --git a/lang/nl-NL.json b/lang/nl-NL.json index f36289cc..44bb697c 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Tracking code", "message.go-to-settings": "Naar instellingen", "message.incorrect-username-password": "Incorrecte gebruikersnaam/wachtwoord.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Een nieuwe versie van umami {version} is beschikbaar!", "message.no-data-available": "Geen gegevens beschikbaar.", "message.no-websites-configured": "Je hebt geen websites ingesteld.", diff --git a/lang/pt-PT.json b/lang/pt-PT.json index d1f7ee3c..1e291452 100644 --- a/lang/pt-PT.json +++ b/lang/pt-PT.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Obter código de tracking", "message.go-to-settings": "Ir para as definições", "message.incorrect-username-password": "Nome de utilizador/palavra-passe incorretos.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Uma nova versão de umami {version} está disponível!", "message.no-data-available": "Sem dados disponíveis.", "message.no-websites-configured": "Não tens nenhum website configurado.", diff --git a/lang/ro-RO.json b/lang/ro-RO.json index 3fe2fb92..c6f738ec 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Obține codul de urmărire", "message.go-to-settings": "Mergi la Setări", "message.incorrect-username-password": "Username/parolă incorecte.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Este disponibilă o nouă versiune {version} de umami!", "message.no-data-available": "Nicio informație disponibilă.", "message.no-websites-configured": "Nu aveți niciun site web configurat.", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 4b0ff88b..afa1bc59 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Получить код отслеживания", "message.go-to-settings": "Перейти к настройкам", "message.incorrect-username-password": "Неверное имя пользователя/пароль.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Доступна новая версия umami {version}", "message.no-data-available": "Нет данных.", "message.no-websites-configured": "У вас нет настроенных сайтов.", diff --git a/lang/sv-SE.json b/lang/sv-SE.json index 6133383b..d1245348 100644 --- a/lang/sv-SE.json +++ b/lang/sv-SE.json @@ -58,7 +58,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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.", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index 1aff9d2e..b2ea3127 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -58,7 +58,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.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "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", diff --git a/lang/uk-UA.json b/lang/uk-UA.json index 27d04279..26f2f548 100644 --- a/lang/uk-UA.json +++ b/lang/uk-UA.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "Отримати код для відслідковування", "message.go-to-settings": "Перейти до налаштувань", "message.incorrect-username-password": "Невірне ім'я користувача або пароль.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "Нова версія umami {version} доступна!", "message.no-data-available": "Немає даних.", "message.no-websites-configured": "У вас немає налаштованих веб-сайтів.", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 18a82ac4..55b342e4 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -58,7 +58,7 @@ "message.get-tracking-code": "获得跟踪代码", "message.go-to-settings": "去设置", "message.incorrect-username-password": "用户名密码不正确.", - "message.log.visitor": "A visitor from {country} using {browser} on {os} {device}", + "message.log.visitor": "Visitor from {country} using {browser} on {os} {device}", "message.new-version-available": "A new version of umami {version} is available!", "message.no-data-available": "无可用数据.", "message.no-websites-configured": "你还没有设置任何网站.", From 689184bf2f5ac17925c263c8606cbe5e17556a08 Mon Sep 17 00:00:00 2001 From: Florian Kapfenberger Date: Sat, 10 Oct 2020 11:36:44 +0200 Subject: [PATCH 49/71] fix: link to github releases without tag --- components/layout/Footer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/layout/Footer.js b/components/layout/Footer.js index 62472f4b..73e010bc 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -25,9 +25,7 @@ export default function Footer() { />
- - {`v${current}`} - + {`v${current}`}
From b72a4c001c86475df3988711f33e9c30371400e2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 10 Oct 2020 11:04:07 -0700 Subject: [PATCH 50/71] Refactored realtime API. Add dot component and colored dots in log. --- components/common/Dot.js | 15 +++++ components/common/Dot.module.css | 17 ++++++ components/metrics/ActiveUsers.js | 3 +- components/metrics/ActiveUsers.module.css | 8 --- components/metrics/RealtimeHeader.js | 8 ++- components/metrics/RealtimeLog.js | 13 ++++ components/pages/RealtimeDashboard.js | 8 +-- lib/format.js | 16 +++++ lib/queries.js | 27 +++++++++ pages/api/realtime.js | 72 ----------------------- pages/api/realtime/init.js | 26 ++++++++ pages/api/realtime/update.js | 26 ++++++++ 12 files changed, 153 insertions(+), 86 deletions(-) create mode 100644 components/common/Dot.js create mode 100644 components/common/Dot.module.css delete mode 100644 pages/api/realtime.js create mode 100644 pages/api/realtime/init.js create mode 100644 pages/api/realtime/update.js diff --git a/components/common/Dot.js b/components/common/Dot.js new file mode 100644 index 00000000..3f424820 --- /dev/null +++ b/components/common/Dot.js @@ -0,0 +1,15 @@ +import React from 'react'; +import classNames from 'classnames'; +import styles from './Dot.module.css'; + +export default function Dot({ color, size, className }) { + return ( +
+ ); +} diff --git a/components/common/Dot.module.css b/components/common/Dot.module.css new file mode 100644 index 00000000..9081dc5c --- /dev/null +++ b/components/common/Dot.module.css @@ -0,0 +1,17 @@ +.dot { + background: var(--green400); + width: 10px; + height: 10px; + border-radius: 100%; + margin-right: 10px; +} + +.dot.small { + width: 8px; + height: 8px; +} + +.dot.large { + width: 16px; + height: 16px; +} diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index c099d8f7..3b691c10 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import useFetch from 'hooks/useFetch'; +import Dot from 'components/common/Dot'; import styles from './ActiveUsers.module.css'; export default function ActiveUsers({ websiteId, token, className }) { @@ -19,7 +20,7 @@ export default function ActiveUsers({ websiteId, token, className }) { return (
-
+
, value: 0 }, - ].concat(websites.map(({ name, website_id }) => ({ label: name, value: website_id }))); + ].concat( + websites.map(({ name, website_id }, index) => ({ + label: name, + value: website_id, + divider: index === 0, + })), + ); const { pageviews, sessions, events, countries } = data; diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 80c14e37..7bb37e08 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -11,7 +11,9 @@ import { BROWSERS } from 'lib/constants'; import Bolt from 'assets/bolt.svg'; import Visitor from 'assets/visitor.svg'; import Eye from 'assets/eye.svg'; +import { stringToColor } from 'lib/format'; import styles from './RealtimeLog.module.css'; +import Dot from '../common/Dot'; const TYPE_PAGEVIEW = 0; const TYPE_SESSION = 1; @@ -26,11 +28,19 @@ const TYPE_ICONS = { export default function RealtimeLog({ data, websites }) { const [locale] = useLocale(); const countryNames = useCountryNames(locale); + const logs = useMemo(() => { const { pageviews, sessions, events } = data; return [...pageviews, ...sessions, ...events].sort(firstBy('created_at', -1)); }, [data]); + const uuids = useMemo(() => { + return data.sessions.reduce((obj, { session_id, session_uuid }) => { + obj[session_id] = session_uuid; + return obj; + }, {}); + }, [data]); + function getType({ view_id, session_id, event_id }) { if (event_id) { return TYPE_EVENT; @@ -88,6 +98,9 @@ export default function RealtimeLog({ data, websites }) { const row = logs[index]; return (
+
+ +
{format(new Date(row.created_at), 'h:mm:ss')}
diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index 30bf1ee4..d514e348 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -16,7 +16,7 @@ import useCountryNames from 'hooks/useCountryNames'; import { FormattedMessage } from 'react-intl'; const REALTIME_RANGE = 30; -const REALTIME_INTERVAL = 55000; +const REALTIME_INTERVAL = 3000; function mergeData(state, data, time) { const ids = state.map(({ __id }) => __id); @@ -34,9 +34,9 @@ export default function RealtimeDashboard() { const countryNames = useCountryNames(locale); const [data, setData] = useState(); const [websiteId, setWebsiteId] = useState(0); - const { data: init, loading } = useFetch('/api/realtime', { params: { type: 'init' } }); - const { data: updates } = useFetch('/api/realtime', { - params: { type: 'update', start_at: data?.timestamp }, + const { data: init, loading } = useFetch('/api/realtime/init'); + const { data: updates } = useFetch('/api/realtime/update', { + params: { start_at: data?.timestamp }, disabled: !init?.websites?.length || !data, interval: REALTIME_INTERVAL, headers: { 'x-umami-token': init?.token }, diff --git a/lib/format.js b/lib/format.js index b031509b..a336c1c4 100644 --- a/lib/format.js +++ b/lib/format.js @@ -62,3 +62,19 @@ export function formatLongNumber(value) { return formatNumber(n); } + +export function stringToColor(str) { + if (!str) { + return '#ffffff'; + } + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let color = '#'; + for (let i = 0; i < 3; i++) { + let value = (hash >> (i * 8)) & 0xff; + color += ('00' + value.toString(16)).substr(-2); + } + return color; +} diff --git a/lib/queries.js b/lib/queries.js index ecf351b1..ed97eb93 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -500,3 +500,30 @@ export function getEventMetrics( params, ); } + +export async function getRealtimeData(websites, time) { + const [pageviews, sessions, events] = await Promise.all([ + getPageviews(websites, time), + getSessions(websites, time), + getEvents(websites, time), + ]); + + return { + pageviews: pageviews.map(({ view_id, ...props }) => ({ + __id: `p${view_id}`, + view_id, + ...props, + })), + sessions: sessions.map(({ session_id, ...props }) => ({ + __id: `s${session_id}`, + session_id, + ...props, + })), + events: events.map(({ event_id, ...props }) => ({ + __id: `e${event_id}`, + event_id, + ...props, + })), + timestamp: Date.now(), + }; +} diff --git a/pages/api/realtime.js b/pages/api/realtime.js deleted file mode 100644 index 538f611a..00000000 --- a/pages/api/realtime.js +++ /dev/null @@ -1,72 +0,0 @@ -import { subMinutes } from 'date-fns'; -import { useAuth } from 'lib/middleware'; -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); - - async function getData(websites, time) { - const [pageviews, sessions, events] = await Promise.all([ - getPageviews(websites, time), - getSessions(websites, time), - getEvents(websites, time), - ]); - - return { - pageviews: pageviews.map(({ view_id, ...props }) => ({ - __id: `p${view_id}`, - view_id, - ...props, - })), - sessions: sessions.map(({ session_id, ...props }) => ({ - __id: `s${session_id}`, - session_id, - ...props, - })), - events: events.map(({ event_id, ...props }) => ({ - __id: `e${event_id}`, - event_id, - ...props, - })), - timestamp: Date.now(), - }; - } - - if (req.method === 'GET') { - const { type, start_at } = req.query; - const { user_id } = req.auth; - - if (type === 'init') { - const websites = await getUserWebsites(user_id); - const ids = websites.map(({ website_id }) => website_id); - const token = await createToken({ websites: ids }); - const data = await getData(ids, subMinutes(new Date(), 30)); - - return ok(res, { - websites, - token, - data, - }); - } - - if (type === 'update') { - const token = req.headers['x-umami-token']; - - if (!token) { - return badRequest(res); - } - - const { websites } = await parseToken(token); - - const data = await getData(websites, new Date(+start_at)); - - return ok(res, data); - } - - return badRequest(res); - } - - return methodNotAllowed(res); -}; diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js new file mode 100644 index 00000000..52e66600 --- /dev/null +++ b/pages/api/realtime/init.js @@ -0,0 +1,26 @@ +import { subMinutes } from 'date-fns'; +import { useAuth } from 'lib/middleware'; +import { ok, methodNotAllowed } from 'lib/response'; +import { getUserWebsites, getRealtimeData } from 'lib/queries'; +import { createToken } from 'lib/crypto'; + +export default async (req, res) => { + await useAuth(req, res); + + if (req.method === 'GET') { + const { user_id } = req.auth; + + const websites = await getUserWebsites(user_id); + const ids = websites.map(({ website_id }) => website_id); + const token = await createToken({ websites: ids }); + const data = await getRealtimeData(ids, subMinutes(new Date(), 30)); + + return ok(res, { + websites, + token, + data, + }); + } + + return methodNotAllowed(res); +}; diff --git a/pages/api/realtime/update.js b/pages/api/realtime/update.js new file mode 100644 index 00000000..15582f81 --- /dev/null +++ b/pages/api/realtime/update.js @@ -0,0 +1,26 @@ +import { useAuth } from 'lib/middleware'; +import { ok, methodNotAllowed, badRequest } from 'lib/response'; +import { getRealtimeData } from 'lib/queries'; +import { parseToken } from 'lib/crypto'; + +export default async (req, res) => { + await useAuth(req, res); + + if (req.method === 'GET') { + const { start_at } = req.query; + + const token = req.headers['x-umami-token']; + + if (!token) { + return badRequest(res); + } + + const { websites } = await parseToken(token); + + const data = await getRealtimeData(websites, new Date(+start_at)); + + return ok(res, data); + } + + return methodNotAllowed(res); +}; From 1fcb610bdda9c96ea27c8a21a5d1636930a0971e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 10 Oct 2020 22:36:55 -0700 Subject: [PATCH 51/71] Updated fetch hook. --- components/metrics/RealtimeLog.js | 14 +++++++++-- components/metrics/RealtimeLog.module.css | 5 +--- components/pages/RealtimeDashboard.js | 29 +++++++++++++---------- hooks/useFetch.js | 25 ++++++++++++------- lib/constants.js | 2 ++ redux/actions/app.js | 12 +++++++--- 6 files changed, 57 insertions(+), 30 deletions(-) diff --git a/components/metrics/RealtimeLog.js b/components/metrics/RealtimeLog.js index 7bb37e08..6e205e16 100644 --- a/components/metrics/RealtimeLog.js +++ b/components/metrics/RealtimeLog.js @@ -94,14 +94,24 @@ export default function RealtimeLog({ data, websites }) { } } + function getTime({ created_at }) { + return format(new Date(created_at), 'h:mm:ss'); + } + + function getColor(row) { + const { session_id } = row; + + return stringToColor(uuids[session_id] || `${session_id}${getWebsite(row)}`); + } + const Row = ({ index, style }) => { const row = logs[index]; return (
- +
-
{format(new Date(row.created_at), 'h:mm:ss')}
+
{getTime(row)}
{getDetail(row)} diff --git a/components/metrics/RealtimeLog.module.css b/components/metrics/RealtimeLog.module.css index 5924b331..99335a52 100644 --- a/components/metrics/RealtimeLog.module.css +++ b/components/metrics/RealtimeLog.module.css @@ -34,15 +34,12 @@ .website { text-align: right; - padding-right: 20px; + padding: 0 20px; } .detail { display: flex; flex: 1; -} - -.detail span { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index d514e348..d80304d9 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -80,7 +80,7 @@ export default function RealtimeDashboard() { ); } return []; - }, [realtimeData]); + }, [realtimeData?.sessions]); const referrers = useMemo(() => { if (realtimeData?.pageviews) { @@ -89,12 +89,15 @@ export default function RealtimeDashboard() { .reduce((arr, { referrer }) => { if (referrer?.startsWith('http')) { const { hostname } = new URL(referrer); - const row = arr.find(({ x }) => x === hostname); - if (!row) { - arr.push({ x: hostname, y: 1 }); - } else { - row.y += 1; + if (!data.domains.includes(hostname)) { + const row = arr.find(({ x }) => x === hostname); + + if (!row) { + arr.push({ x: hostname, y: 1 }); + } else { + row.y += 1; + } } } return arr; @@ -103,7 +106,7 @@ export default function RealtimeDashboard() { ); } return []; - }, [realtimeData]); + }, [realtimeData?.pageviews]); useEffect(() => { if (init && !data) { @@ -111,7 +114,11 @@ export default function RealtimeDashboard() { const domains = init.websites.map(({ domain }) => domain); setData({ websites, domains, ...data }); - } else if (updates) { + } + }, [init]); + + useEffect(() => { + if (updates) { const { pageviews, sessions, events, timestamp } = updates; const time = subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(); @@ -123,16 +130,14 @@ export default function RealtimeDashboard() { timestamp, })); } - }, [init, updates]); + }, [updates]); - if (!init || loading || !data) { + if (!init || !data || loading) { return null; } const { websites } = data; - //console.log({realtimeData, countries}); - return ( { if (url && !disabled) { - setTimeout(() => loadData(), delay); + const id = setTimeout(() => loadData(params), delay); + + return () => { + clearTimeout(id); + }; } - }, [url, disabled, ...update]); + }, [url, !!disabled, count, ...update]); useEffect(() => { - const id = interval ? setInterval(() => loadData(), interval) : null; + if (interval && !disabled) { + const id = setInterval(() => setCount(state => state + 1), interval); - return () => { - clearInterval(id); - }; - }, [interval, params]); + return () => { + clearInterval(id); + }; + } + }, [interval, !!disabled]); return { data, status, error, loading }; } diff --git a/lib/constants.js b/lib/constants.js index 721f5e1c..80d8eed1 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -51,6 +51,8 @@ export const EVENT_COLORS = [ '#ffec16', ]; +export const DEFAULT_LOCALE = 'en-US'; +export const DEFAULT_THEME = 'light'; export const DEFAUL_CHART_HEIGHT = 400; export const DEFAULT_ANIMATION_DURATION = 300; export const DEFAULT_DATE_RANGE = '24hour'; diff --git a/redux/actions/app.js b/redux/actions/app.js index 94b77daa..900be56d 100644 --- a/redux/actions/app.js +++ b/redux/actions/app.js @@ -1,13 +1,19 @@ import { createSlice } from '@reduxjs/toolkit'; import { getItem } from 'lib/web'; -import { LOCALE_CONFIG, THEME_CONFIG, VERSION_CHECK } from 'lib/constants'; +import { + DEFAULT_LOCALE, + DEFAULT_THEME, + LOCALE_CONFIG, + THEME_CONFIG, + VERSION_CHECK, +} from 'lib/constants'; import semver from 'semver'; const app = createSlice({ name: 'app', initialState: { - locale: getItem(LOCALE_CONFIG) || 'en-US', - theme: getItem(THEME_CONFIG) || 'light', + locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE, + theme: getItem(THEME_CONFIG) || DEFAULT_THEME, versions: { current: process.env.VERSION, latest: null, From 4119e80a9a0fa76841d2ddfeb31c03e4173bc4ae Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 11 Oct 2020 01:33:26 -0700 Subject: [PATCH 52/71] Update table components. --- components/metrics/BrowsersTable.js | 4 ++-- components/metrics/CountriesTable.js | 6 +++--- components/metrics/DataTable.js | 18 +++++++++--------- components/metrics/DevicesTable.js | 4 ++-- components/metrics/EventsTable.js | 5 ++--- components/metrics/MetricsTable.js | 8 +++++--- components/metrics/PagesTable.js | 4 ++-- components/metrics/ReferrersTable.js | 4 ++-- components/pages/RealtimeDashboard.js | 18 ++++++++---------- components/pages/WebsiteDetails.js | 4 ++-- pages/api/website/[id]/metrics.js | 2 +- 11 files changed, 38 insertions(+), 39 deletions(-) diff --git a/components/metrics/BrowsersTable.js b/components/metrics/BrowsersTable.js index 97f9bfbd..3481449c 100644 --- a/components/metrics/BrowsersTable.js +++ b/components/metrics/BrowsersTable.js @@ -3,15 +3,15 @@ import { FormattedMessage } from 'react-intl'; import MetricsTable from './MetricsTable'; import { browserFilter } from 'lib/filters'; -export default function BrowsersTable({ websiteId, token, limit }) { +export default function BrowsersTable({ websiteId, token, ...props }) { return ( } type="browser" metric={} websiteId={websiteId} token={token} - limit={limit} dataFilter={browserFilter} /> ); diff --git a/components/metrics/CountriesTable.js b/components/metrics/CountriesTable.js index d562b464..5b44bd23 100644 --- a/components/metrics/CountriesTable.js +++ b/components/metrics/CountriesTable.js @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl'; import useCountryNames from 'hooks/useCountryNames'; import useLocale from 'hooks/useLocale'; -export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) { +export default function CountriesTable({ websiteId, token, onDataLoad, ...props }) { const [locale] = useLocale(); const countryNames = useCountryNames(locale); @@ -15,13 +15,13 @@ export default function CountriesTable({ websiteId, token, limit, onDataLoad = ( return ( } type="country" metric={} websiteId={websiteId} token={token} - limit={limit} - onDataLoad={data => onDataLoad(percentFilter(data))} + onDataLoad={data => onDataLoad?.(percentFilter(data))} renderLabel={renderLabel} /> ); diff --git a/components/metrics/DataTable.js b/components/metrics/DataTable.js index 9e12d887..70eae89f 100644 --- a/components/metrics/DataTable.js +++ b/components/metrics/DataTable.js @@ -11,10 +11,10 @@ export default function DataTable({ title, metric, className, - limit, renderLabel, height = 400, animate = true, + virtualize = false, }) { const [format, setFormat] = useState(true); const formatFunc = format ? formatLongNumber : formatNumber; @@ -30,7 +30,7 @@ export default function DataTable({ label={renderLabel ? renderLabel(row) : label} value={value} percent={percent} - animate={animate} + animate={animate && !virtualize} format={formatFunc} onClick={handleSetFormat} /> @@ -51,13 +51,13 @@ export default function DataTable({
{data?.length === 0 && } - {limit - ? data.map(row => getRow(row)) - : data.length > 0 && ( - - {Row} - - )} + {virtualize && data.length > 0 ? ( + + {Row} + + ) : ( + data.map(row => getRow(row)) + )}
); diff --git a/components/metrics/DevicesTable.js b/components/metrics/DevicesTable.js index 7d87d1c1..e71355db 100644 --- a/components/metrics/DevicesTable.js +++ b/components/metrics/DevicesTable.js @@ -4,15 +4,15 @@ import { deviceFilter } from 'lib/filters'; import { FormattedMessage } from 'react-intl'; import { getDeviceMessage } from 'components/messages'; -export default function DevicesTable({ websiteId, token, limit }) { +export default function DevicesTable({ websiteId, token, ...props }) { return ( } type="device" metric={} websiteId={websiteId} token={token} - limit={limit} dataFilter={deviceFilter} renderLabel={({ x }) => getDeviceMessage(x)} /> diff --git a/components/metrics/EventsTable.js b/components/metrics/EventsTable.js index 43b52c4d..73df5028 100644 --- a/components/metrics/EventsTable.js +++ b/components/metrics/EventsTable.js @@ -3,17 +3,16 @@ import { FormattedMessage } from 'react-intl'; import MetricsTable from './MetricsTable'; import Tag from 'components/common/Tag'; -export default function EventsTable({ websiteId, token, limit, onDataLoad }) { +export default function EventsTable({ websiteId, token, ...props }) { return ( } type="event" metric={} websiteId={websiteId} token={token} - limit={limit} renderLabel={({ x }) =>