+ {error &&
}
}
options={menuOptions}
value={locale}
- menuClassname={styles.menu}
+ menuClassName={styles.menu}
renderValue={option => option?.display}
onSelect={handleSelect}
/>
diff --git a/hooks/useFetch.js b/hooks/useFetch.js
index 907f45af..0eb82b13 100644
--- a/hooks/useFetch.js
+++ b/hooks/useFetch.js
@@ -25,7 +25,13 @@ export default function useFetch(url, params = {}, options = {}) {
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
- setData(data);
+ if (status >= 400) {
+ setError(data);
+ setData(null);
+ } else {
+ setData(data);
+ }
+
setStatus(status);
onDataLoad(data);
} catch (e) {
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/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.",
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/next.config.js b/next.config.js
index c1e31d7b..e98c091c 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({
@@ -19,4 +17,17 @@ module.exports = {
return config;
},
+ async headers() {
+ return [
+ {
+ source: '/umami.js',
+ headers: [
+ {
+ key: 'Cache-Control',
+ value: 'public, max-age=2592000', // 30 days
+ },
+ ],
+ },
+ ]
+ },
};
diff --git a/package.json b/package.json
index 9c6e899e..aca8f8c0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "0.74.0",
+ "version": "0.80.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 (
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);
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)