mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-18 15:23:38 +01:00
commit
5ecaf5587b
@ -4,7 +4,7 @@
|
|||||||
"es2020": true,
|
"es2020": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "prettier/react"],
|
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
|
1
assets/chart-bar.svg
Normal file
1
assets/chart-bar.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z"/></svg>
|
After Width: | Height: | Size: 885 B |
@ -7,12 +7,14 @@ import styles from './Checkbox.module.css';
|
|||||||
function Checkbox({ name, value, label, onChange }) {
|
function Checkbox({ name, value, label, onChange }) {
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
|
|
||||||
|
const onClick = () => ref.current.click();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.checkbox} onClick={() => ref.current.click()}>
|
<div className={styles.checkbox} onClick={onClick}>
|
||||||
{value && <Icon icon={<Check />} size="small" />}
|
{value && <Icon icon={<Check />} size="small" />}
|
||||||
</div>
|
</div>
|
||||||
<label className={styles.label} htmlFor={name}>
|
<label className={styles.label} htmlFor={name} onClick={onClick}>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -20,7 +22,7 @@ function Checkbox({ name, value, label, onChange }) {
|
|||||||
className={styles.input}
|
className={styles.input}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name={name}
|
name={name}
|
||||||
value={value}
|
defaultChecked={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
.label {
|
.label {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
user-select: none; /* disable text selection when clicking to toggle the checkbox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
@ -77,7 +77,7 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<label></label>
|
<label />
|
||||||
<Field name="enable_share_url">
|
<Field name="enable_share_url">
|
||||||
{({ field }) => (
|
{({ field }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -70,6 +70,11 @@
|
|||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
font-size: var(--font-size-normal);
|
font-size: var(--font-size-normal);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -102,6 +107,9 @@
|
|||||||
.burger {
|
.burger {
|
||||||
display: block;
|
display: block;
|
||||||
/* color: #4a4a4a; */
|
/* color: #4a4a4a; */
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--gray900);
|
||||||
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 3.25rem;
|
height: 3.25rem;
|
||||||
width: 3.25rem;
|
width: 3.25rem;
|
||||||
@ -112,20 +120,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.burger span {
|
.burger span {
|
||||||
transform: translateX(-50%);
|
transform: translateX(25%);
|
||||||
padding: 1px 0px;
|
padding: 1px 0px;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
width: 20px;
|
width: 65%;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: white;
|
background-color: var(--gray900);
|
||||||
}
|
}
|
||||||
|
|
||||||
.burger div {
|
.burger div {
|
||||||
height: 100%;
|
/* height: 100%; */
|
||||||
color: white;
|
color: var(--gray900);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
transform: translateX(-50%);
|
/* transform: translateX(-50%); */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ export default function WebsiteChart({
|
|||||||
domain,
|
domain,
|
||||||
stickyHeader = false,
|
stickyHeader = false,
|
||||||
showLink = false,
|
showLink = false,
|
||||||
|
hideChart = false,
|
||||||
onDataLoad = () => {},
|
onDataLoad = () => {},
|
||||||
}) {
|
}) {
|
||||||
const shareToken = useShareToken();
|
const shareToken = useShareToken();
|
||||||
@ -91,13 +92,15 @@ export default function WebsiteChart({
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
{error && <ErrorMessage />}
|
{error && <ErrorMessage />}
|
||||||
<PageviewsChart
|
{!hideChart && (
|
||||||
websiteId={websiteId}
|
<PageviewsChart
|
||||||
data={chartData}
|
websiteId={websiteId}
|
||||||
unit={unit}
|
data={chartData}
|
||||||
records={getDateLength(startDate, endDate, unit)}
|
unit={unit}
|
||||||
loading={loading}
|
records={getDateLength(startDate, endDate, unit)}
|
||||||
/>
|
loading={loading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Link from 'components/common/Link';
|
import Link from 'components/common/Link';
|
||||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
|
import Button from 'components/common/Button';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import Arrow from 'assets/arrow-right.svg';
|
import Arrow from 'assets/arrow-right.svg';
|
||||||
|
import Chart from 'assets/chart-bar.svg';
|
||||||
import styles from './WebsiteList.module.css';
|
import styles from './WebsiteList.module.css';
|
||||||
|
|
||||||
export default function WebsiteList({ userId }) {
|
export default function WebsiteList({ userId }) {
|
||||||
const { data } = useFetch('/api/websites', { params: { user_id: userId } });
|
const { data } = useFetch('/api/websites', { params: { user_id: userId } });
|
||||||
|
const [hideCharts, setHideCharts] = useState(false);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (data.length === 0) {
|
||||||
<Page>
|
return (
|
||||||
{data.map(({ website_id, name, domain }) => (
|
<Page>
|
||||||
<div key={website_id} className={styles.website}>
|
|
||||||
<WebsiteChart websiteId={website_id} title={name} domain={domain} showLink />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{data.length === 0 && (
|
|
||||||
<EmptyPlaceholder
|
<EmptyPlaceholder
|
||||||
msg={
|
msg={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -35,7 +33,26 @@ export default function WebsiteList({ userId }) {
|
|||||||
<FormattedMessage id="message.go-to-settings" defaultMessage="Go to settings" />
|
<FormattedMessage id="message.go-to-settings" defaultMessage="Go to settings" />
|
||||||
</Link>
|
</Link>
|
||||||
</EmptyPlaceholder>
|
</EmptyPlaceholder>
|
||||||
)}
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<div className={styles.menubar}>
|
||||||
|
<Button icon={<Chart />} onClick={() => setHideCharts(!hideCharts)} />
|
||||||
|
</div>
|
||||||
|
{data.map(({ website_id, name, domain }) => (
|
||||||
|
<div key={website_id} className={styles.website}>
|
||||||
|
<WebsiteChart
|
||||||
|
websiteId={website_id}
|
||||||
|
title={name}
|
||||||
|
domain={domain}
|
||||||
|
hideChart={hideCharts}
|
||||||
|
showLink
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,3 +9,10 @@
|
|||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menubar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
@ -5,7 +5,13 @@ import { THEME_CONFIG } from 'lib/constants';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function useTheme() {
|
export default function useTheme() {
|
||||||
const theme = useSelector(state => state.app.theme || getItem(THEME_CONFIG) || 'light');
|
const defaultTheme =
|
||||||
|
typeof window !== 'undefined'
|
||||||
|
? window?.matchMedia('prefers-color-scheme: dark')?.matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light'
|
||||||
|
: 'light';
|
||||||
|
const theme = useSelector(state => state.app.theme || getItem(THEME_CONFIG) || defaultTheme);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
function saveTheme(value) {
|
function saveTheme(value) {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"label.administrator": "Administrator",
|
"label.administrator": "Administrator",
|
||||||
"label.all": "Alles",
|
"label.all": "Alles",
|
||||||
"label.all-websites": "Alle websites",
|
"label.all-websites": "Alle websites",
|
||||||
|
"label.all-events": "Alle gebeurtenissen",
|
||||||
"label.back": "Terug",
|
"label.back": "Terug",
|
||||||
"label.cancel": "Annuleren",
|
"label.cancel": "Annuleren",
|
||||||
"label.change-password": "Wachtwoord wijzigen",
|
"label.change-password": "Wachtwoord wijzigen",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"label.administrator": "Администратор",
|
"label.administrator": "Администратор",
|
||||||
"label.all": "Все",
|
"label.all": "Все",
|
||||||
"label.all-websites": "Все сайты",
|
"label.all-websites": "Все сайты",
|
||||||
|
"label.all-events": "Все события",
|
||||||
"label.back": "Назад",
|
"label.back": "Назад",
|
||||||
"label.cancel": "Отменить",
|
"label.cancel": "Отменить",
|
||||||
"label.change-password": "Изменить пароль",
|
"label.change-password": "Изменить пароль",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"label.administrator": "Адміністратор",
|
"label.administrator": "Адміністратор",
|
||||||
"label.all": "Всі",
|
"label.all": "Всі",
|
||||||
"label.all-websites": "Всі сайти",
|
"label.all-websites": "Всі сайти",
|
||||||
|
"label.all-events": "Всі події",
|
||||||
"label.back": "Назад",
|
"label.back": "Назад",
|
||||||
"label.cancel": "Відмінити",
|
"label.cancel": "Відмінити",
|
||||||
"label.change-password": "Змінити пароль",
|
"label.change-password": "Змінити пароль",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { v4, v5, validate } from 'uuid';
|
import { v4, v5, validate } from 'uuid';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcryptjs';
|
||||||
import { JWT, JWE, JWK } from 'jose';
|
import { JWT, JWE, JWK } from 'jose';
|
||||||
import { startOfMonth } from 'date-fns';
|
import { startOfMonth } from 'date-fns';
|
||||||
|
|
||||||
@ -40,11 +40,11 @@ export function getRandomChars(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function hashPassword(password) {
|
export async function hashPassword(password) {
|
||||||
return bcrypt.hash(password, SALT_ROUNDS);
|
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkPassword(password, hash) {
|
export async function checkPassword(password, hash) {
|
||||||
return bcrypt.compare(password, hash);
|
return bcrypt.compareSync(password, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createToken(payload) {
|
export async function createToken(payload) {
|
||||||
|
@ -18,7 +18,7 @@ export const urlFilter = (data, { raw }) => {
|
|||||||
return `${pathname}${search}`;
|
return `${pathname}${search}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return removeTrailingSlash(pathname);
|
return pathname;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
MOBILE_SCREEN_WIDTH,
|
MOBILE_SCREEN_WIDTH,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
let lookup;
|
||||||
|
|
||||||
export function getIpAddress(req) {
|
export function getIpAddress(req) {
|
||||||
// Cloudflare
|
// Cloudflare
|
||||||
if (req.headers['cf-connecting-ip']) {
|
if (req.headers['cf-connecting-ip']) {
|
||||||
@ -61,7 +63,9 @@ export async function getCountry(req, ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Database lookup
|
// Database lookup
|
||||||
const lookup = await maxmind.open(path.resolve('./public/geo/GeoLite2-Country.mmdb'));
|
if (!lookup) {
|
||||||
|
lookup = await maxmind.open(path.resolve('./public/geo/GeoLite2-Country.mmdb'));
|
||||||
|
}
|
||||||
|
|
||||||
const result = lookup.get(ip);
|
const result = lookup.get(ip);
|
||||||
|
|
||||||
|
51
package.json
51
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "1.16.0",
|
"version": "1.17.0",
|
||||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||||
"author": "Mike Cao <mike@mikecao.com>",
|
"author": "Mike Cao <mike@mikecao.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -56,12 +56,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "2.19.0",
|
"@prisma/client": "2.21.2",
|
||||||
"@reduxjs/toolkit": "^1.5.0",
|
"@reduxjs/toolkit": "^1.5.1",
|
||||||
"bcrypt": "^5.0.0",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.1",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.3.1",
|
||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"date-fns": "^2.16.1",
|
"date-fns": "^2.16.1",
|
||||||
@ -70,27 +70,28 @@
|
|||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"formik": "^2.2.6",
|
"formik": "^2.2.6",
|
||||||
"immer": "^8.0.1",
|
"immer": "^8.0.1",
|
||||||
|
"ipaddr.js": "^2.0.0",
|
||||||
"is-localhost-ip": "^1.4.0",
|
"is-localhost-ip": "^1.4.0",
|
||||||
"isbot": "^3.0.25",
|
"isbot": "^3.0.26",
|
||||||
"jose": "2.0.3",
|
"jose": "2.0.5",
|
||||||
"maxmind": "^4.3.1",
|
"maxmind": "^4.3.1",
|
||||||
"moment-timezone": "^0.5.32",
|
"moment-timezone": "^0.5.33",
|
||||||
"next": "^10.0.9",
|
"next": "^10.1.3",
|
||||||
"prompts": "2.4.0",
|
"prompts": "2.4.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-intl": "^5.14.1",
|
"react-intl": "^5.16.0",
|
||||||
"react-redux": "^7.2.3",
|
"react-redux": "^7.2.4",
|
||||||
"react-simple-maps": "^2.3.0",
|
"react-simple-maps": "^2.3.0",
|
||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
"react-tooltip": "^4.2.17",
|
"react-tooltip": "^4.2.18",
|
||||||
"react-use-measure": "^2.0.4",
|
"react-use-measure": "^2.0.4",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.1.0",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"request-ip": "^2.1.3",
|
"request-ip": "^2.1.3",
|
||||||
"semver": "^7.3.4",
|
"semver": "^7.3.5",
|
||||||
"thenby": "^1.3.4",
|
"thenby": "^1.3.4",
|
||||||
"timezone-support": "^2.0.2",
|
"timezone-support": "^2.0.2",
|
||||||
"tinycolor2": "^1.4.2",
|
"tinycolor2": "^1.4.2",
|
||||||
@ -99,16 +100,16 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^2.13.16",
|
"@formatjs/cli": "^2.13.16",
|
||||||
"@rollup/plugin-buble": "^0.21.3",
|
"@rollup/plugin-buble": "^0.21.3",
|
||||||
"@rollup/plugin-node-resolve": "^11.1.1",
|
"@rollup/plugin-node-resolve": "^11.2.1",
|
||||||
"@rollup/plugin-replace": "^2.3.4",
|
"@rollup/plugin-replace": "^2.3.4",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"del": "^6.0.0",
|
"del": "^6.0.0",
|
||||||
"dotenv-cli": "^4.0.0",
|
"dotenv-cli": "^4.0.0",
|
||||||
"eslint": "^7.20.0",
|
"eslint": "^7.25.0",
|
||||||
"eslint-config-prettier": "^7.2.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.23.2",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"extract-react-intl-messages": "^4.1.1",
|
"extract-react-intl-messages": "^4.1.1",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
@ -120,14 +121,14 @@
|
|||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"prettier-eslint": "^12.0.0",
|
"prettier-eslint": "^12.0.0",
|
||||||
"prisma": "2.19.0",
|
"prisma": "2.21.2",
|
||||||
"rollup": "^2.38.3",
|
"rollup": "^2.45.2",
|
||||||
"rollup-plugin-hashbang": "^2.2.2",
|
"rollup-plugin-hashbang": "^2.2.2",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"stylelint": "^13.10.0",
|
"stylelint": "^13.13.0",
|
||||||
"stylelint-config-css-modules": "^2.2.0",
|
"stylelint-config-css-modules": "^2.2.0",
|
||||||
"stylelint-config-prettier": "^8.0.1",
|
"stylelint-config-prettier": "^8.0.1",
|
||||||
"stylelint-config-recommended": "^3.0.0",
|
"stylelint-config-recommended": "^5.0.0",
|
||||||
"tar": "^6.0.5"
|
"tar": "^6.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import isbot from 'isbot';
|
import isbot from 'isbot';
|
||||||
|
import ipaddr from 'ipaddr.js';
|
||||||
import { savePageView, saveEvent } from 'lib/queries';
|
import { savePageView, saveEvent } from 'lib/queries';
|
||||||
import { useCors, useSession } from 'lib/middleware';
|
import { useCors, useSession } from 'lib/middleware';
|
||||||
import { getIpAddress } from 'lib/request';
|
import { getIpAddress } from 'lib/request';
|
||||||
@ -15,8 +16,21 @@ export default async (req, res) => {
|
|||||||
if (process.env.IGNORE_IP) {
|
if (process.env.IGNORE_IP) {
|
||||||
const ips = process.env.IGNORE_IP.split(',').map(n => n.trim());
|
const ips = process.env.IGNORE_IP.split(',').map(n => n.trim());
|
||||||
const ip = getIpAddress(req);
|
const ip = getIpAddress(req);
|
||||||
|
const blocked = ips.find(i => {
|
||||||
|
if (i === ip) return true;
|
||||||
|
|
||||||
if (ips.includes(ip)) {
|
// CIDR notation
|
||||||
|
if (i.indexOf('/') > 0) {
|
||||||
|
const addr = ipaddr.parse(ip);
|
||||||
|
const range = ipaddr.parseCIDR(i);
|
||||||
|
|
||||||
|
if (addr.match(range)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (blocked) {
|
||||||
return ok(res);
|
return ok(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user