mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 02:06:19 +01:00
Use next-basics package.
This commit is contained in:
parent
1a6af8fc41
commit
f4e0da481e
@ -4,7 +4,12 @@
|
||||
"es2020": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:prettier/recommended", "next"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/recommended",
|
||||
"next"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
@ -12,7 +17,27 @@
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"alias": {
|
||||
"map": [
|
||||
["assets", "./assets"],
|
||||
["components", "./components"],
|
||||
["db", "./db"],
|
||||
["hooks", "./hooks"],
|
||||
["lang", "./lang"],
|
||||
["lib", "./lib"],
|
||||
["public", "./public"],
|
||||
["queries", "./queries"],
|
||||
["store", "./store"],
|
||||
["styles", "./styles"]
|
||||
],
|
||||
"extensions": [".ts", ".js", ".jsx", ".json"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "error",
|
||||
"react/display-name": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import Icon from './Icon';
|
||||
import External from 'assets/arrow-up-right-from-square.svg';
|
||||
import Icon from './Icon';
|
||||
import styles from './FilterLink.module.css';
|
||||
|
||||
export default function FilterLink({ id, value, label, externalUrl }) {
|
||||
@ -25,7 +25,7 @@ export default function FilterLink({ id, value, label, externalUrl }) {
|
||||
</a>
|
||||
</Link>
|
||||
{externalUrl && (
|
||||
<a href={externalUrl} target="_blank" rel="noreferrer noopener" className={styles.link}>
|
||||
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
|
||||
<Icon icon={<External />} className={styles.icon} />
|
||||
</a>
|
||||
)}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { setItem } from 'next-basics';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import useStore, { checkVersion } from 'store/version';
|
||||
import { setItem } from 'lib/web';
|
||||
import { REPO_URL, VERSION_CHECK } from 'lib/constants';
|
||||
import Button from './Button';
|
||||
import styles from './UpdateNotice.module.css';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { setItem } from 'next-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, {
|
||||
@ -11,7 +12,6 @@ import FormLayout, {
|
||||
} from 'components/layout/FormLayout';
|
||||
import Icon from 'components/common/Icon';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { setItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import { setUser } from 'store/app';
|
||||
import Logo from 'assets/logo.svg';
|
||||
|
@ -9,7 +9,7 @@ import HamburgerButton from 'components/common/HamburgerButton';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import UserButton from 'components/settings/UserButton';
|
||||
import { HOMEPAGE_URL } from 'lib/constants';
|
||||
import useConfig from '/hooks/useConfig';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useUser from 'hooks/useUser';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Header.module.css';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Button from 'components/common/Button';
|
||||
import Times from 'assets/times.svg';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import styles from './FilterTags.module.css';
|
||||
|
||||
export default function FilterTags({ params, onClick }) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Tag from 'components/common/Tag';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import { paramFilter } from 'lib/filters';
|
||||
import { safeDecodeURI } from 'lib/url';
|
||||
import FilterButtons from '../common/FilterButtons';
|
||||
import MetricsTable from './MetricsTable';
|
||||
|
||||
const FILTER_COMBINED = 0;
|
||||
const FILTER_RAW = 1;
|
||||
|
@ -23,8 +23,6 @@ export default function DashboardEdit({ websites }) {
|
||||
|
||||
const ordered = useMemo(() => sortArrayByMap(websites, order, 'website_id'), [websites, order]);
|
||||
|
||||
console.log({ order, ordered });
|
||||
|
||||
function handleWebsiteDrag({ destination, source }) {
|
||||
if (!destination || destination.index === source.index) return;
|
||||
|
||||
|
@ -28,8 +28,6 @@ export default function TestConsole() {
|
||||
const website = data.find(({ website_id }) => website_id === +websiteId);
|
||||
const selectedValue = options.find(({ value }) => value === website?.website_id)?.value;
|
||||
|
||||
console.log({ websiteId, data, options, website });
|
||||
|
||||
function handleSelect(value) {
|
||||
router.push(`/console/${value}`);
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ export default function WebsiteList({ websites, showCharts, limit }) {
|
||||
const { websiteOrder } = useDashboard();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
console.log({ websiteOrder });
|
||||
|
||||
const ordered = useMemo(
|
||||
() => sortArrayByMap(websites, websiteOrder, 'website_id'),
|
||||
[websites, websiteOrder],
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import { removeItem } from 'next-basics';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
import Icon from 'components/common/Icon';
|
||||
import User from 'assets/user.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
import { removeItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get, post, put, del, getItem } from 'lib/web';
|
||||
import { get, post, put, del, getItem } from 'next-basics';
|
||||
import { AUTH_TOKEN, SHARE_TOKEN_HEADER } from 'lib/constants';
|
||||
import useStore from 'store/app';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get } from 'lib/web';
|
||||
import { get } from 'next-basics';
|
||||
import enUS from 'public/intl/country/en-US.json';
|
||||
|
||||
const countryNames = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import { getItem, setItem } from 'lib/web';
|
||||
import { getItem, setItem } from 'next-basics';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
import useLocale from './useLocale';
|
||||
|
@ -8,7 +8,7 @@ export default function useFetch(url, options = {}, update = []) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [count, setCount] = useState(0);
|
||||
const { get } = useApi();
|
||||
const { params = {}, headers = {}, disabled, delay = 0, interval, onDataLoad } = options;
|
||||
const { params = {}, headers = {}, disabled = false, delay = 0, interval, onDataLoad } = options;
|
||||
|
||||
async function loadData(params) {
|
||||
try {
|
||||
@ -29,7 +29,9 @@ export default function useFetch(url, options = {}, update = []) {
|
||||
|
||||
onDataLoad?.(data);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
||||
setError(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -44,7 +46,7 @@ export default function useFetch(url, options = {}, update = []) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}
|
||||
}, [url, !!disabled, count, ...update]);
|
||||
}, [url, disabled, count, ...update]);
|
||||
|
||||
useEffect(() => {
|
||||
if (interval && !disabled) {
|
||||
@ -54,7 +56,7 @@ export default function useFetch(url, options = {}, update = []) {
|
||||
clearInterval(id);
|
||||
};
|
||||
}
|
||||
}, [interval, !!disabled]);
|
||||
}, [interval, disabled]);
|
||||
|
||||
return { ...response, error, loading };
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get } from 'lib/web';
|
||||
import { get } from 'next-basics';
|
||||
import enUS from 'public/intl/language/en-US.json';
|
||||
|
||||
const languageNames = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { get, setItem } from 'lib/web';
|
||||
import { get, setItem } from 'next-basics';
|
||||
import { LOCALE_CONFIG } from 'lib/constants';
|
||||
import { getDateLocale, getTextDirection } from 'lib/lang';
|
||||
import useStore, { setLocale } from 'store/app';
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getQueryString } from 'lib/url';
|
||||
|
||||
function getQueryString(params) {
|
||||
return new URLSearchParams({ ...params }).toString();
|
||||
}
|
||||
|
||||
export default function usePageQuery() {
|
||||
const router = useRouter();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import useStore, { setTheme } from 'store/app';
|
||||
import { getItem, setItem } from 'lib/web';
|
||||
import { getItem, setItem } from 'next-basics';
|
||||
import { THEME_CONFIG } from 'lib/constants';
|
||||
|
||||
const selector = state => state.theme;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { getTimezone } from 'lib/date';
|
||||
import { getItem, setItem } from 'lib/web';
|
||||
import { getItem, setItem } from 'next-basics';
|
||||
import { TIMEZONE_CONFIG } from 'lib/constants';
|
||||
|
||||
export default function useTimezone() {
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { parseSecureToken, parseToken } from './crypto';
|
||||
import { parseSecureToken, parseToken } from 'next-basics';
|
||||
import { SHARE_TOKEN_HEADER } from './constants';
|
||||
import { getWebsiteById } from 'queries';
|
||||
import { secret } from './crypto';
|
||||
|
||||
export async function getAuthToken(req) {
|
||||
try {
|
||||
const token = req.headers.authorization;
|
||||
|
||||
return parseSecureToken(token.split(' ')[1]);
|
||||
return parseSecureToken(token.split(' ')[1], secret());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@ -14,7 +15,7 @@ export async function getAuthToken(req) {
|
||||
|
||||
export async function isValidToken(token, validation) {
|
||||
try {
|
||||
const result = await parseToken(token);
|
||||
const result = parseToken(token, secret());
|
||||
|
||||
if (typeof validation === 'object') {
|
||||
return !Object.keys(validation).find(key => result[key] !== validation[key]);
|
||||
|
@ -12,13 +12,9 @@ export const CLICKHOUSE_DATE_FORMATS = {
|
||||
year: '%Y-01-01',
|
||||
};
|
||||
|
||||
const log = debug('clickhouse');
|
||||
const log = debug('umami:clickhouse');
|
||||
|
||||
function getClient() {
|
||||
if (!process.env.CLICKHOUSE_URL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
hostname,
|
||||
port,
|
||||
@ -149,13 +145,13 @@ function parseFilters(table, column, filters = {}, params = [], sessionKey = 'se
|
||||
};
|
||||
}
|
||||
|
||||
function replaceQuery(string, params = []) {
|
||||
let formattedString = string;
|
||||
function formatQuery(str, params = []) {
|
||||
let formattedString = str;
|
||||
|
||||
params.forEach((a, i) => {
|
||||
let replace = a;
|
||||
params.forEach((param, i) => {
|
||||
let replace = param;
|
||||
|
||||
if (typeof a === 'string' || a instanceof String) {
|
||||
if (typeof param === 'string' || param instanceof String) {
|
||||
replace = `'${replace}'`;
|
||||
}
|
||||
|
||||
@ -165,11 +161,11 @@ function replaceQuery(string, params = []) {
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
async function rawQuery(query, params = [], debug = false) {
|
||||
let formattedQuery = replaceQuery(query, params);
|
||||
async function rawQuery(query, params = []) {
|
||||
let formattedQuery = formatQuery(query, params);
|
||||
|
||||
if (debug || process.env.LOG_QUERY) {
|
||||
console.log(formattedQuery);
|
||||
if (process.env.LOG_QUERY) {
|
||||
log(formattedQuery);
|
||||
}
|
||||
|
||||
return clickhouse.query(formattedQuery).toPromise();
|
||||
@ -188,7 +184,7 @@ async function findFirst(data) {
|
||||
}
|
||||
|
||||
// Initialization
|
||||
const clickhouse = global[CLICKHOUSE] || getClient();
|
||||
const clickhouse = process.env.CLICKHOUSE_URL && (global[CLICKHOUSE] || getClient());
|
||||
|
||||
export default {
|
||||
client: clickhouse,
|
||||
@ -199,8 +195,7 @@ export default {
|
||||
getBetweenDates,
|
||||
getFilterQuery,
|
||||
parseFilters,
|
||||
replaceQuery,
|
||||
rawQuery,
|
||||
findUnique,
|
||||
findFirst,
|
||||
rawQuery,
|
||||
};
|
||||
|
@ -1,24 +1,15 @@
|
||||
import crypto from 'crypto';
|
||||
import { v4, v5, validate } from 'uuid';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { JWT, JWE, JWK } from 'jose';
|
||||
import { v4, v5 } from 'uuid';
|
||||
import { startOfMonth } from 'date-fns';
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
const KEY = JWK.asKey(Buffer.from(secret()));
|
||||
const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString());
|
||||
const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
export function hash(...args) {
|
||||
return crypto.createHash('sha512').update(args.join('')).digest('hex');
|
||||
}
|
||||
import { hash } from 'next-basics';
|
||||
|
||||
export function secret() {
|
||||
return hash(process.env.HASH_SALT || process.env.DATABASE_URL);
|
||||
}
|
||||
|
||||
export function salt() {
|
||||
return v5(hash(secret(), ROTATING_SALT), v5.DNS);
|
||||
const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString());
|
||||
|
||||
return hash([secret(), ROTATING_SALT]);
|
||||
}
|
||||
|
||||
export function uuid(...args) {
|
||||
@ -26,49 +17,3 @@ export function uuid(...args) {
|
||||
|
||||
return v5(args.join(''), salt());
|
||||
}
|
||||
|
||||
export function isValidUuid(s) {
|
||||
return validate(s);
|
||||
}
|
||||
|
||||
export function getRandomChars(n) {
|
||||
let s = '';
|
||||
for (let i = 0; i < n; i++) {
|
||||
s += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export function hashPassword(password) {
|
||||
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export function checkPassword(password, hash) {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
|
||||
export async function createToken(payload) {
|
||||
return JWT.sign(payload, KEY);
|
||||
}
|
||||
|
||||
export async function parseToken(token) {
|
||||
try {
|
||||
return JWT.verify(token, KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSecureToken(payload) {
|
||||
return JWE.encrypt(await createToken(payload), KEY);
|
||||
}
|
||||
|
||||
export async function parseSecureToken(token) {
|
||||
try {
|
||||
const result = await JWE.decrypt(token, KEY);
|
||||
|
||||
return parseToken(result.toString());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ export const MYSQL = 'mysql';
|
||||
export const CLICKHOUSE = 'clickhouse';
|
||||
export const KAFKA = 'kafka';
|
||||
export const KAFKA_PRODUCER = 'kafka-producer';
|
||||
export const REDIS = 'redis';
|
||||
|
||||
// Fixes issue with converting bigint values
|
||||
BigInt.prototype.toJSON = function () {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { removeWWW } from './url';
|
||||
|
||||
export const urlFilter = data => {
|
||||
const isValidUrl = url => {
|
||||
return url !== '' && url !== null && !url.startsWith('#');
|
||||
@ -49,7 +47,7 @@ export const refFilter = data => {
|
||||
try {
|
||||
const url = new URL(x);
|
||||
|
||||
id = removeWWW(url.hostname) || url.href;
|
||||
id = url.hostname.replace('www', '') || url.href;
|
||||
} catch {
|
||||
id = '';
|
||||
}
|
||||
@ -94,11 +92,7 @@ export const paramFilter = data => {
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const d = Object.keys(map).flatMap(key =>
|
||||
return Object.keys(map).flatMap(key =>
|
||||
Object.keys(map[key]).map(n => ({ x: `${key}=${n}`, p: key, v: n, y: map[key][n] })),
|
||||
);
|
||||
|
||||
console.log({ map, d });
|
||||
|
||||
return d;
|
||||
};
|
||||
|
13
lib/kafka.js
13
lib/kafka.js
@ -3,13 +3,9 @@ import dateFormat from 'dateformat';
|
||||
import debug from 'debug';
|
||||
import { KAFKA, KAFKA_PRODUCER } from 'lib/db';
|
||||
|
||||
const log = debug('kafka');
|
||||
const log = debug('umami:kafka');
|
||||
|
||||
function getClient() {
|
||||
if (!process.env.KAFKA_URL || !process.env.KAFKA_BROKER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { username, password } = new URL(process.env.KAFKA_URL);
|
||||
const brokers = process.env.KAFKA_BROKER.split(',');
|
||||
|
||||
@ -73,8 +69,11 @@ let kafka;
|
||||
let producer;
|
||||
|
||||
(async () => {
|
||||
kafka = global[KAFKA] || getClient();
|
||||
producer = global[KAFKA_PRODUCER] || (await getProducer());
|
||||
kafka = process.env.KAFKA_URL && process.env.KAFKA_BROKER && (global[KAFKA] || getClient());
|
||||
|
||||
if (kafka) {
|
||||
producer = global[KAFKA_PRODUCER] || (await getProducer());
|
||||
}
|
||||
})();
|
||||
|
||||
export default {
|
||||
|
@ -1,19 +1,7 @@
|
||||
import { createMiddleware, unauthorized, badRequest, serverError } from 'next-basics';
|
||||
import cors from 'cors';
|
||||
import { getSession } from './session';
|
||||
import { getAuthToken } from './auth';
|
||||
import { unauthorized, badRequest, serverError } from './response';
|
||||
|
||||
export function createMiddleware(middleware) {
|
||||
return (req, res) =>
|
||||
new Promise((resolve, reject) => {
|
||||
middleware(req, res, result => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const useCors = createMiddleware(cors());
|
||||
|
||||
@ -23,7 +11,9 @@ export const useSession = createMiddleware(async (req, res, next) => {
|
||||
try {
|
||||
session = await getSession(req);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
||||
return serverError(res, e.message);
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,8 @@ import { PrismaClient } from '@prisma/client';
|
||||
import chalk from 'chalk';
|
||||
import moment from 'moment-timezone';
|
||||
import debug from 'debug';
|
||||
import { PRISMA, MYSQL, POSTGRESQL } from 'lib/db';
|
||||
import { PRISMA, MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
|
||||
import { FILTER_IGNORED } from 'lib/constants';
|
||||
import { getDatabaseType } from 'lib/db';
|
||||
|
||||
const MYSQL_DATE_FORMATS = {
|
||||
minute: '%Y-%m-%d %H:%i:00',
|
||||
@ -22,7 +21,7 @@ const POSTGRESQL_DATE_FORMATS = {
|
||||
year: 'YYYY-01-01',
|
||||
};
|
||||
|
||||
const log = debug('prisma');
|
||||
const log = debug('umami:prisma');
|
||||
|
||||
const PRISMA_OPTIONS = {
|
||||
log: [
|
||||
|
19
lib/redis.js
19
lib/redis.js
@ -1,10 +1,11 @@
|
||||
import { createClient } from 'redis';
|
||||
import { startOfMonth } from 'date-fns';
|
||||
import { getSessions, getAllWebsites } from '/queries';
|
||||
import debug from 'debug';
|
||||
import { getSessions, getAllWebsites } from 'queries';
|
||||
import { REDIS } from 'lib/db';
|
||||
|
||||
const log = debug('db:redis');
|
||||
const REDIS = Symbol.for('redis');
|
||||
const log = debug('umami:redis');
|
||||
const INITIALIZED = 'redis:initialized';
|
||||
|
||||
async function getClient() {
|
||||
const redis = new createClient({
|
||||
@ -38,7 +39,7 @@ async function stageData() {
|
||||
await addRedis(sessionUuids);
|
||||
await addRedis(websiteIds);
|
||||
|
||||
await redis.set('initialized', 'initialized');
|
||||
await redis.set(INITIALIZED, 1);
|
||||
}
|
||||
|
||||
async function addRedis(ids) {
|
||||
@ -52,12 +53,12 @@ async function addRedis(ids) {
|
||||
let redis = null;
|
||||
|
||||
(async () => {
|
||||
redis = global[REDIS] || (await getClient());
|
||||
redis = process.env.REDIS_URL && (global[REDIS] || (await getClient()));
|
||||
|
||||
const value = await redis.get('initialized');
|
||||
|
||||
if (!value) {
|
||||
await stageData();
|
||||
if (redis) {
|
||||
if (!(await redis.get(INITIALIZED))) {
|
||||
await stageData();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
export function ok(res, data = {}) {
|
||||
return json(res, data);
|
||||
}
|
||||
|
||||
export function json(res, data = {}) {
|
||||
return res.status(200).json(data);
|
||||
}
|
||||
|
||||
export function send(res, data, type = 'text/plain') {
|
||||
res.setHeader('Content-Type', type);
|
||||
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
export function redirect(res, url) {
|
||||
res.setHeader('Location', url);
|
||||
|
||||
return res.status(303).end();
|
||||
}
|
||||
|
||||
export function badRequest(res, msg = '400 Bad Request') {
|
||||
return res.status(400).end(msg);
|
||||
}
|
||||
|
||||
export function unauthorized(res, msg = '401 Unauthorized') {
|
||||
return res.status(401).end(msg);
|
||||
}
|
||||
|
||||
export function forbidden(res, msg = '403 Forbidden') {
|
||||
return res.status(403).end(msg);
|
||||
}
|
||||
|
||||
export function notFound(res, msg = '404 Not Found') {
|
||||
return res.status(404).end(msg);
|
||||
}
|
||||
|
||||
export function methodNotAllowed(res, msg = '405 Method Not Allowed') {
|
||||
res.status(405).end(msg);
|
||||
}
|
||||
|
||||
export function serverError(res, msg = '500 Internal Server Error') {
|
||||
res.status(500).end(msg);
|
||||
}
|
8
lib/security.js
Normal file
8
lib/security.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { getItem } from 'next-basics';
|
||||
import { AUTH_TOKEN } from './constants';
|
||||
|
||||
export function getAuthHeader() {
|
||||
const token = getItem(AUTH_TOKEN);
|
||||
|
||||
return token ? { authorization: `Bearer ${token}` } : {};
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { isValidUuid, parseToken, uuid } from 'lib/crypto';
|
||||
import { parseToken } from 'next-basics';
|
||||
import { validate } from 'uuid';
|
||||
import { uuid } from 'lib/crypto';
|
||||
import redis from 'lib/redis';
|
||||
import { getClientInfo, getJsonBody } from 'lib/request';
|
||||
import { createSession, getSessionByUuid, getWebsiteByUuid } from 'queries';
|
||||
@ -22,8 +24,8 @@ export async function getSession(req) {
|
||||
|
||||
const { website: website_uuid, hostname, screen, language } = payload;
|
||||
|
||||
if (!isValidUuid(website_uuid)) {
|
||||
throw new Error(`Invalid website: ${website_uuid}`);
|
||||
if (!validate(website_uuid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let websiteId = null;
|
||||
@ -52,7 +54,6 @@ export async function getSession(req) {
|
||||
if (process.env.REDIS_URL) {
|
||||
sessionCreated = (await redis.get(`session:${session_uuid}`)) !== null;
|
||||
} else {
|
||||
console.log('test');
|
||||
session = await getSessionByUuid(session_uuid);
|
||||
sessionCreated = !!session;
|
||||
sessionId = session ? session.session_id : null;
|
||||
@ -60,7 +61,6 @@ export async function getSession(req) {
|
||||
|
||||
if (!sessionCreated) {
|
||||
try {
|
||||
console.log('test2');
|
||||
session = await createSession(websiteId, {
|
||||
session_uuid,
|
||||
hostname,
|
||||
|
35
lib/url.js
35
lib/url.js
@ -1,35 +0,0 @@
|
||||
export function removeTrailingSlash(url) {
|
||||
return url && url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url;
|
||||
}
|
||||
|
||||
export function removeWWW(url) {
|
||||
return url && url.length > 1 && url.startsWith('www.') ? url.slice(4) : url;
|
||||
}
|
||||
|
||||
export function getQueryString(params = {}) {
|
||||
const map = Object.keys(params).reduce((arr, key) => {
|
||||
if (params[key] !== undefined) {
|
||||
return arr.concat(`${key}=${encodeURIComponent(params[key])}`);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
if (map.length) {
|
||||
return `?${map.join('&')}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function makeUrl(url, params) {
|
||||
return `${url}${getQueryString(params)}`;
|
||||
}
|
||||
|
||||
export function safeDecodeURI(s) {
|
||||
try {
|
||||
return decodeURI(s);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return s;
|
||||
}
|
78
lib/web.js
78
lib/web.js
@ -1,78 +0,0 @@
|
||||
import { makeUrl } from './url';
|
||||
|
||||
export const apiRequest = (method, url, body, headers) => {
|
||||
return fetch(url, {
|
||||
method,
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body,
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
return res.json().then(data => ({ ok: res.ok, status: res.status, data }));
|
||||
}
|
||||
|
||||
return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
|
||||
});
|
||||
};
|
||||
|
||||
export const get = (url, params, headers) =>
|
||||
apiRequest('get', makeUrl(url, params), undefined, headers);
|
||||
|
||||
export const del = (url, params, headers) =>
|
||||
apiRequest('delete', makeUrl(url, params), undefined, headers);
|
||||
|
||||
export const post = (url, params, headers) =>
|
||||
apiRequest('post', url, JSON.stringify(params), headers);
|
||||
|
||||
export const put = (url, params, headers) =>
|
||||
apiRequest('put', url, JSON.stringify(params), headers);
|
||||
|
||||
export const hook = (_this, method, callback) => {
|
||||
const orig = _this[method];
|
||||
|
||||
return (...args) => {
|
||||
callback.apply(null, args);
|
||||
|
||||
return orig.apply(_this, args);
|
||||
};
|
||||
};
|
||||
|
||||
export const doNotTrack = () => {
|
||||
const { doNotTrack, navigator, external } = window;
|
||||
|
||||
const msTrackProtection = 'msTrackingProtectionEnabled';
|
||||
const msTracking = () => {
|
||||
return external && msTrackProtection in external && external[msTrackProtection]();
|
||||
};
|
||||
|
||||
const dnt = doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack || msTracking();
|
||||
|
||||
return dnt == '1' || dnt === 'yes';
|
||||
};
|
||||
|
||||
export const setItem = (key, data, session) => {
|
||||
if (typeof window !== 'undefined' && data) {
|
||||
(session ? sessionStorage : localStorage).setItem(key, JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
export const getItem = (key, session) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const value = (session ? sessionStorage : localStorage).getItem(key);
|
||||
|
||||
if (value !== 'undefined') {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeItem = (key, session) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
(session ? sessionStorage : localStorage).removeItem(key);
|
||||
}
|
||||
};
|
@ -58,7 +58,6 @@
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "4.5.7",
|
||||
"@prisma/client": "4.2.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chart.js": "^2.9.4",
|
||||
"classnames": "^2.3.1",
|
||||
@ -81,11 +80,11 @@
|
||||
"is-docker": "^3.0.0",
|
||||
"is-localhost-ip": "^1.4.0",
|
||||
"isbot": "^3.4.5",
|
||||
"jose": "2.0.5",
|
||||
"kafkajs": "^2.1.0",
|
||||
"maxmind": "^4.3.6",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"next": "^12.2.5",
|
||||
"next-basics": "^0.6.0",
|
||||
"node-fetch": "^3.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prop-types": "^15.7.2",
|
||||
@ -115,6 +114,8 @@
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-next": "^12.2.4",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^7.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getAccountById, deleteAccount } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ok, unauthorized, methodNotAllowed, badRequest, hashPassword } from 'next-basics';
|
||||
import { getAccountById, getAccountByUsername, updateAccount, createAccount } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { hashPassword } from 'lib/crypto';
|
||||
import { ok, unauthorized, methodNotAllowed, badRequest } from 'lib/response';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { getAccountById, updateAccount } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { badRequest, methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { checkPassword, hashPassword } from 'lib/crypto';
|
||||
import {
|
||||
badRequest,
|
||||
methodNotAllowed,
|
||||
ok,
|
||||
unauthorized,
|
||||
checkPassword,
|
||||
hashPassword,
|
||||
} from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getAccounts } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, unauthorized, methodNotAllowed } from 'lib/response';
|
||||
import { ok, unauthorized, methodNotAllowed } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { checkPassword, createSecureToken } from 'lib/crypto';
|
||||
import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics';
|
||||
import { getAccountByUsername } from 'queries/admin/account/getAccountByUsername';
|
||||
import { ok, unauthorized, badRequest } from 'lib/response';
|
||||
import { secret } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
@ -11,10 +11,10 @@ export default async (req, res) => {
|
||||
|
||||
const account = await getAccountByUsername(username);
|
||||
|
||||
if (account && (await checkPassword(password, account.password))) {
|
||||
if (account && checkPassword(password, account.password)) {
|
||||
const { user_id, username, is_admin } = account;
|
||||
const user = { user_id, username, is_admin };
|
||||
const token = await createSecureToken(user);
|
||||
const token = createSecureToken(user, secret());
|
||||
|
||||
return ok(res, { token, user });
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, unauthorized } from 'lib/response';
|
||||
import { ok, unauthorized } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,12 +1,10 @@
|
||||
const { Resolver } = require('dns').promises;
|
||||
import isbot from 'isbot';
|
||||
import ipaddr from 'ipaddr.js';
|
||||
import { createToken, unauthorized, send, badRequest, forbidden } from 'next-basics';
|
||||
import { savePageView, saveEvent } from 'queries';
|
||||
import { useCors, useSession } from 'lib/middleware';
|
||||
import { getJsonBody, getIpAddress } from 'lib/request';
|
||||
import { unauthorized, send, badRequest, forbidden } from 'lib/response';
|
||||
import { createToken } from 'lib/crypto';
|
||||
import { removeTrailingSlash } from 'lib/url';
|
||||
import { uuid } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
@ -69,7 +67,7 @@ export default async (req, res) => {
|
||||
let { url, referrer, event_name, event_data } = payload;
|
||||
|
||||
if (process.env.REMOVE_TRAILING_SLASH) {
|
||||
url = removeTrailingSlash(url);
|
||||
url = url.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
const event_uuid = uuid();
|
||||
@ -89,7 +87,7 @@ export default async (req, res) => {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const token = await createToken({ website_id, session_id, session_uuid });
|
||||
const token = createToken({ website_id, session_id, session_uuid });
|
||||
|
||||
return send(res, token);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ok, methodNotAllowed } from 'lib/response';
|
||||
import { ok, methodNotAllowed } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ok } from 'lib/response';
|
||||
import { ok } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
return ok(res, 'nice');
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { subMinutes } from 'date-fns';
|
||||
import { ok, methodNotAllowed, createToken } from 'next-basics';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, methodNotAllowed } from 'lib/response';
|
||||
import { getUserWebsites, getRealtimeData } from 'queries';
|
||||
import { createToken } from 'lib/crypto';
|
||||
import { secret } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
@ -12,7 +12,7 @@ export default async (req, res) => {
|
||||
|
||||
const websites = await getUserWebsites(user_id);
|
||||
const ids = websites.map(({ website_id }) => website_id);
|
||||
const token = await createToken({ websites: ids });
|
||||
const token = createToken({ websites: ids }, secret());
|
||||
const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
|
||||
|
||||
return ok(res, {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ok, methodNotAllowed, badRequest, parseToken } from 'next-basics';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, methodNotAllowed, badRequest } from 'lib/response';
|
||||
import { getRealtimeData } from 'queries';
|
||||
import { parseToken } from 'lib/crypto';
|
||||
import { SHARE_TOKEN_HEADER } from 'lib/constants';
|
||||
import { secret } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
@ -16,7 +16,7 @@ export default async (req, res) => {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const { websites } = await parseToken(token);
|
||||
const { websites } = parseToken(token, secret());
|
||||
|
||||
const data = await getRealtimeData(websites, new Date(+start_at));
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getWebsiteByShareId } from 'queries';
|
||||
import { ok, notFound, methodNotAllowed } from 'lib/response';
|
||||
import { createToken } from 'lib/crypto';
|
||||
import { ok, notFound, methodNotAllowed, createToken } from 'next-basics';
|
||||
import { secret } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.query;
|
||||
@ -10,7 +10,7 @@ export default async (req, res) => {
|
||||
|
||||
if (website) {
|
||||
const websiteId = website.website_id;
|
||||
const token = await createToken({ website_id: websiteId });
|
||||
const token = createToken({ website_id: websiteId }, secret());
|
||||
|
||||
return ok(res, { websiteId, token });
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
import { getActiveVisitors } from 'queries';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { getEventMetrics } from 'queries';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { deleteWebsite, getWebsiteById } from 'queries';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'queries';
|
||||
import { ok, methodNotAllowed, unauthorized, badRequest } from 'lib/response';
|
||||
import { ok, methodNotAllowed, unauthorized, badRequest } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
import { FILTER_IGNORED } from 'lib/constants';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { getPageviewStats } from 'queries';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { resetWebsite } from 'queries';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
export default async (req, res) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getWebsiteStats } from 'queries';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
import { useCors } from 'lib/middleware';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ok, unauthorized, methodNotAllowed, getRandomChars } from 'next-basics';
|
||||
import { updateWebsite, createWebsite, getWebsiteById } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { uuid, getRandomChars } from 'lib/crypto';
|
||||
import { ok, unauthorized, methodNotAllowed } from 'lib/response';
|
||||
import { uuid } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getAllWebsites, getUserWebsites } from 'queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { ok, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { removeItem } from 'lib/web';
|
||||
import { removeItem } from 'next-basics';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import { setUser } from 'store/app';
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* eslint-disable no-console */
|
||||
require('dotenv').config();
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { hashPassword } = require('next-basics');
|
||||
const chalk = require('chalk');
|
||||
const prompts = require('prompts');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
const runQuery = async query => {
|
||||
return query.catch(e => {
|
||||
@ -24,10 +24,6 @@ const updateAccountByUsername = (username, data) => {
|
||||
);
|
||||
};
|
||||
|
||||
const hashPassword = password => {
|
||||
return bcrypt.hashSync(password, SALT_ROUNDS);
|
||||
};
|
||||
|
||||
const changePassword = async (username, newPassword) => {
|
||||
const password = hashPassword(newPassword);
|
||||
return updateAccountByUsername(username, { password });
|
||||
|
@ -1,6 +1,6 @@
|
||||
import create from 'zustand';
|
||||
import { DEFAULT_LOCALE, DEFAULT_THEME, LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants';
|
||||
import { getItem } from 'lib/web';
|
||||
import { getItem } from 'next-basics';
|
||||
|
||||
const initialState = {
|
||||
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import create from 'zustand';
|
||||
import { DASHBOARD_CONFIG, DEFAULT_WEBSITE_LIMIT } from 'lib/constants';
|
||||
import { getItem, setItem } from 'lib/web';
|
||||
import { getItem, setItem } from 'next-basics';
|
||||
|
||||
export const initialState = {
|
||||
showCharts: true,
|
||||
|
@ -2,7 +2,7 @@ import create from 'zustand';
|
||||
import produce from 'immer';
|
||||
import semver from 'semver';
|
||||
import { CURRENT_VERSION, VERSION_CHECK, UPDATES_URL } from 'lib/constants';
|
||||
import { getItem } from 'lib/web';
|
||||
import { getItem } from 'next-basics';
|
||||
|
||||
const initialState = {
|
||||
current: CURRENT_VERSION,
|
||||
|
107
yarn.lock
107
yarn.lock
@ -1440,11 +1440,6 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@panva/asn1.js@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz"
|
||||
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
|
||||
|
||||
"@prisma/client@4.2.1":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.2.1.tgz#b384587f6066070381ea4c90228a14697a0c271b"
|
||||
@ -2373,6 +2368,11 @@ buble@^0.20.0:
|
||||
minimist "^1.2.5"
|
||||
regexpu-core "4.5.4"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
|
||||
@ -3020,6 +3020,13 @@ ecc-jsbn@~0.1.1:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
electron-to-chromium@^1.4.118:
|
||||
version "1.4.143"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz"
|
||||
@ -3144,6 +3151,11 @@ eslint-config-prettier@^8.5.0:
|
||||
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz"
|
||||
integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
|
||||
|
||||
eslint-import-resolver-alias@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz#297062890e31e4d6651eb5eba9534e1f6e68fc97"
|
||||
integrity sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==
|
||||
|
||||
eslint-import-resolver-node@^0.3.6:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
|
||||
@ -4146,13 +4158,6 @@ jest-worker@^26.2.1:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
jose@2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz"
|
||||
integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==
|
||||
dependencies:
|
||||
"@panva/asn1.js" "^1.0.0"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -4254,6 +4259,22 @@ jsonparse@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||
|
||||
jsonwebtoken@^8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
|
||||
@ -4272,6 +4293,23 @@ jsprim@^1.2.2:
|
||||
array-includes "^3.1.5"
|
||||
object.assign "^4.1.3"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
kafkajs@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-2.2.0.tgz#43b2d13c82395acee4500f09d6c7d503db8c77ea"
|
||||
@ -4395,6 +4433,36 @@ lodash.debounce@^4.0.8:
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
@ -4405,6 +4473,11 @@ lodash.mergewith@^4.6.2:
|
||||
resolved "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz"
|
||||
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash.pick@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz"
|
||||
@ -4662,6 +4735,14 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||
|
||||
next-basics@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.6.0.tgz#bbb3b2dafa69931c3b7aad0cd456332ddcf019c7"
|
||||
integrity sha512-S9deRGhQPj9tN9WSroK8UAcxFuoV38YNFO9B5qEQpt7ZUNCkAUITccW98LGlJ5WfNzkp7dnXVgmL3+yvRWlH4w==
|
||||
dependencies:
|
||||
bcryptjs "^2.4.3"
|
||||
jsonwebtoken "^8.5.1"
|
||||
|
||||
next@^12.2.5:
|
||||
version "12.2.5"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-12.2.5.tgz#14fb5975e8841fad09553b8ef41fe1393602b717"
|
||||
@ -5866,7 +5947,7 @@ semver-compare@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
|
||||
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
Loading…
Reference in New Issue
Block a user