Merge pull request #1064 from mikecao/dev

v1.29.0
This commit is contained in:
Mike Cao authored and GitHub committed 2022-04-03 23:49:39 -07:00
commit 17aaa55aba
156 files changed
+720 -805

No files matched your search

+1 -2
View File
@@ -4,7 +4,7 @@
"es2020": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "next"],
"extends": ["eslint:recommended", "plugin:prettier/recommended", "next"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
@@ -12,7 +12,6 @@
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
+30
View File
@@ -0,0 +1,30 @@
name: Create docker images
on: [create]
jobs:
build:
name: Build, push, and deploy
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
strategy:
matrix:
db-type: [postgresql, mysql]
steps:
- uses: actions/checkout@v2
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- uses: mr-smithers-excellent/docker-build-push@v5
name: Build & push Docker image for ${{ matrix.db-type }}
with:
image: umami
tags: ${{ matrix.db-type }}-${{ env.RELEASE_VERSION }}, ${{ matrix.db-type }}-latest
buildArgs: DATABASE_TYPE=${{ matrix.db-type }}
registry: ghcr.io/${{ github.actor }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+42
View File
@@ -0,0 +1,42 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on: [push]
env:
DATABASE_TYPE: postgresql
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- node-version: 12.x
db-type: postgresql
- node-version: 12.x
db-type: mysql
- node-version: 14.x
db-type: postgresql
- node-version: 14.x
db-type: mysql
- node-version: 16.x
db-type: postgresql
- node-version: 16.x
db-type: mysql
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
env:
DATABASE_TYPE: ${{ matrix.db-type }}
- run: npm install --global yarn
- run: yarn install --frozen-lockfile
- run: yarn build
-42
View File
@@ -1,42 +0,0 @@
on:
push:
branches:
- master
jobs:
build:
name: Build, push, and deploy
runs-on: ubuntu-latest
steps:
- name: Checkout master
uses: actions/checkout@v2
- name: Build PostgreSQL container image
run: |
docker build --build-arg DATABASE_TYPE=postgresql \
--tag ghcr.io/$GITHUB_ACTOR/umami:postgresql-$(echo $GITHUB_SHA | head -c7) \
--tag ghcr.io/$GITHUB_ACTOR/umami:postgresql-latest \
.
- name: Build MySQL container image
run: |
docker build --build-arg DATABASE_TYPE=mysql \
--tag ghcr.io/$GITHUB_ACTOR/umami:mysql-$(echo $GITHUB_SHA | head -c7) \
--tag ghcr.io/$GITHUB_ACTOR/umami:mysql-latest \
.
- name: Docker login
run: >-
echo "${{ secrets.GITHUB_TOKEN }}"
| docker login -u "${{ github.actor }}" --password-stdin ghcr.io
- name: Push image to GitHub
run: |
# Push each image individually, avoiding pushing to umami:latest
# as MySQL or PostgreSQL are required
docker push ghcr.io/$GITHUB_ACTOR/umami:postgresql-$(echo $GITHUB_SHA | head -c7)
docker push ghcr.io/$GITHUB_ACTOR/umami:postgresql-latest
docker push ghcr.io/$GITHUB_ACTOR/umami:mysql-$(echo $GITHUB_SHA | head -c7)
docker push ghcr.io/$GITHUB_ACTOR/umami:mysql-latest
+7
View File
@@ -102,6 +102,13 @@ npm install
npm run build
```
To update the Docker image, simply pull the new images and rebuild:
```bash
docker-compose pull
docker-compose up --force-recreate
```
## License
MIT
+2 -2
View File
@@ -1,2 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 428 389.11">
<circle cx="214.15" cy="181" r="171" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="20"/><path d="M413,134.11H15.29a15,15,0,0,0-15,15v15.3C.12,168,0,171.52,0,175.11c0,118.19,95.81,214,214,214,116.4,0,211.1-92.94,213.93-208.67,0-.44.07-.88.07-1.33v-30A15,15,0,0,0,413,134.11Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 428 389.11" width="20" height="20">
<circle cx="214.15" cy="181" r="171" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="20"/><path d="M413,134.11H15.29a15,15,0,0,0-15,15v15.3C.12,168,0,171.52,0,175.11c0,118.19,95.81,214,214,214,116.4,0,211.1-92.94,213.93-208.67,0-.44.07-.88.07-1.33v-30A15,15,0,0,0,413,134.11Z"/></svg>

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 401 B

+3 -2
View File
@@ -30,7 +30,7 @@ export default function MetricsTable({
const {
resolve,
router,
query: { url },
query: { url, referrer },
} = usePageQuery();
const { data, loading, error } = useFetch(
@@ -41,12 +41,13 @@ export default function MetricsTable({
start_at: +startDate,
end_at: +endDate,
url,
referrer,
},
onDataLoad,
delay: DEFAULT_ANIMATION_DURATION,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
[modified],
[modified, url, referrer],
);
const filteredData = useMemo(() => {
+2 -2
View File
@@ -19,7 +19,7 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
const [filter, setFilter] = useState(FILTER_COMBINED);
const {
resolve,
query: { ref: currentRef },
query: { referrer: currentRef },
} = usePageQuery();
const buttons = [
@@ -37,7 +37,7 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
const renderLink = ({ w: link, x: label }) => {
return (
<div className={styles.row}>
<Link href={resolve({ ref: label })} replace={true}>
<Link href={resolve({ referrer: label })} replace={true}>
<a
className={classNames(styles.label, {
[styles.inactive]: currentRef && label !== currentRef,
+4 -4
View File
@@ -33,7 +33,7 @@ export default function WebsiteChart({
const {
router,
resolve,
query: { url, ref },
query: { url, referrer },
} = usePageQuery();
const { get } = useApi();
@@ -46,12 +46,12 @@ export default function WebsiteChart({
unit,
tz: timezone,
url,
ref,
referrer,
},
onDataLoad,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
[modified, url, ref],
[modified, url, referrer],
);
const chartData = useMemo(() => {
@@ -88,7 +88,7 @@ export default function WebsiteChart({
stickyClassName={styles.sticky}
enabled={stickyHeader}
>
<FilterTags params={{ url, ref }} onClick={handleCloseFilter} />
<FilterTags params={{ url, referrer }} onClick={handleCloseFilter} />
<div className="col-12 col-lg-9">
<MetricsBar websiteId={websiteId} />
</div>
+9 -8
View File
@@ -37,17 +37,18 @@ export default function AccountSettings() {
</Link>
);
const Buttons = row =>
row.username !== 'admin' ? (
<ButtonLayout align="right">
<Button icon={<Pen />} size="small" onClick={() => setEditAccount(row)}>
<FormattedMessage id="label.edit" defaultMessage="Edit" />
</Button>
const Buttons = row => (
<ButtonLayout align="right">
<Button icon={<Pen />} size="small" onClick={() => setEditAccount(row)}>
<FormattedMessage id="label.edit" defaultMessage="Edit" />
</Button>
{!row.is_admin && (
<Button icon={<Trash />} size="small" onClick={() => setDeleteAccount(row)}>
<FormattedMessage id="label.delete" defaultMessage="Delete" />
</Button>
</ButtonLayout>
) : null;
)}
</ButtonLayout>
);
const columns = [
{
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/country/en-US.json';
import enUS from 'public/intl/country/en-US.json';
const countryNames = {
'en-US': enUS,
@@ -12,7 +12,7 @@ export default function useCountryNames(locale) {
const { basePath } = useRouter();
async function loadData(locale) {
const { ok, data } = await get(`${basePath}/country/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/country/${locale}.json`);
if (ok) {
countryNames[locale] = data;
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/language/en-US.json';
import enUS from 'public/intl/language/en-US.json';
const languageNames = {
'en-US': enUS,
@@ -12,7 +12,7 @@ export default function useLanguageNames(locale) {
const { basePath } = useRouter();
async function loadData(locale) {
const { ok, data } = await get(`${basePath}/language/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/language/${locale}.json`);
if (ok) {
languageNames[locale] = data;
+2 -2
View File
@@ -5,7 +5,7 @@ import { LOCALE_CONFIG } from 'lib/constants';
import { getDateLocale, getTextDirection } from 'lib/lang';
import useStore, { setLocale } from 'store/app';
import useForceUpdate from 'hooks/useForceUpdate';
import enUS from 'public/messages/en-US.json';
import enUS from 'public/intl/messages/en-US.json';
const messages = {
'en-US': enUS,
@@ -21,7 +21,7 @@ export default function useLocale() {
const dateLocale = getDateLocale(locale);
async function loadMessages(locale) {
const { ok, data } = await get(`${basePath}/messages/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/messages/${locale}.json`);
if (ok) {
messages[locale] = data;
+2 -2
View File
@@ -28,7 +28,7 @@
"label.enable-share-url": "Activer le partage d'URL",
"label.invalid": "Invalide",
"label.invalid-domain": "Domaine invalide",
"label.language": "Language",
"label.language": "Langage",
"label.last-days": "{x} derniers jours",
"label.last-hours": "{x} dernières heures",
"label.logged-in-as": "Connecté en tant que {username}",
@@ -51,7 +51,7 @@
"label.settings": "Paramètres",
"label.share-url": "Partager l'URL",
"label.single-day": "Journée",
"label.theme": "Theme",
"label.theme": "Thème",
"label.this-month": "Ce mois ci",
"label.this-week": "Cette semaine",
"label.this-year": "Cette année",
+10 -10
View File
@@ -5,7 +5,7 @@
"label.administrator": "Administrator",
"label.all": "Wszystkie",
"label.all-events": "Wszystkie wydarzenia",
"label.all-time": "All time",
"label.all-time": "Cały czas",
"label.all-websites": "Wszystkie witryny",
"label.back": "Powrót",
"label.cancel": "Anuluj",
@@ -14,7 +14,7 @@
"label.copy-to-clipboard": "Skopiuj do schowka",
"label.current-password": "Aktualne hasło",
"label.custom-range": "Zakres niestandardowy",
"label.dashboard": "Dashboard",
"label.dashboard": "Panel",
"label.date-range": "Zakres dat",
"label.default-date-range": "Domyślny zakres dat",
"label.delete": "Usuń",
@@ -28,7 +28,7 @@
"label.enable-share-url": "Włącz udostępnianie adresu URL",
"label.invalid": "Nieprawidłowy",
"label.invalid-domain": "Nieprawidłowa witryna",
"label.language": "Language",
"label.language": "Język",
"label.last-days": "Ostatnie {x} dni",
"label.last-hours": "Ostatnie {x} godzin",
"label.logged-in-as": "Zalogowano jako {username}",
@@ -37,7 +37,7 @@
"label.more": "Więcej",
"label.name": "Nazwa",
"label.new-password": "Nowe hasło",
"label.owner": "Owner",
"label.owner": "Właściciel",
"label.password": "Hasło",
"label.passwords-dont-match": "Hasła się nie zgadzają",
"label.profile": "Profil",
@@ -46,12 +46,12 @@
"label.refresh": "Odśwież",
"label.required": "Wymagany",
"label.reset": "Zresetuj",
"label.reset-website": "Reset statistics",
"label.reset-website": "Zresetuj statystyki",
"label.save": "Zapisz",
"label.settings": "Ustawienia",
"label.share-url": "Udostępnij adres URL",
"label.single-day": "W tym dniu",
"label.theme": "Theme",
"label.theme": "Motyw",
"label.this-month": "W tym miesiącu",
"label.this-week": "W tym tygodniu",
"label.this-year": "W tym roku",
@@ -64,7 +64,7 @@
"label.websites": "Witryny",
"message.active-users": "{x} aktualnie {x, plural, one {odwiedzający} other {odwiedzających}}",
"message.confirm-delete": "Czy na pewno chcesz usunąć {target}?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?",
"message.copied": "Skopiowano!",
"message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.",
"message.failure": "Coś poszło nie tak.",
@@ -78,10 +78,10 @@
"message.no-websites-configured": "Nie masz skonfigurowanych żadnych witryn internetowych.",
"message.page-not-found": "Strona nie znaleziona.",
"message.powered-by": "Obsługiwane przez {name}",
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
"message.reset-warning": "Wszystkie statystyki tej witryny zostaną usunięte, ale kod śledzenia pozostanie nienaruszony.",
"message.save-success": "Zapisano pomyślnie.",
"message.share-url": "To jest publicznie udostępniany adres URL dla {target}.",
"message.toggle-charts": "Toggle charts",
"message.toggle-charts": "Przełącz wykresy",
"message.track-stats": "Aby śledzić statystyki dla {target}, umieść poniższy kod w sekcji {head} swojej witryny.",
"message.type-delete": "Wpisz {delete} w polu poniżej, aby potwierdzić.",
"message.type-reset": "Wpisz {reset} w polu poniżej, aby potwierdzić.",
@@ -99,7 +99,7 @@
"metrics.filter.combined": "Połączone",
"metrics.filter.domain-only": "Tylko domena",
"metrics.filter.raw": "Surowe dane",
"metrics.languages": "Languages",
"metrics.languages": "Języki",
"metrics.operating-systems": "System operacyjny",
"metrics.page-views": "Wyświetlenia strony",
"metrics.pages": "Strony",
+10 -10
View File
@@ -4,8 +4,8 @@
"label.add-website": "添加网站",
"label.administrator": "管理员",
"label.all": "所有",
"label.all-events": "All events",
"label.all-time": "All time",
"label.all-events": "所有事件",
"label.all-time": "所有时间段",
"label.all-websites": "全部网站",
"label.back": "返回",
"label.cancel": "取消",
@@ -28,7 +28,7 @@
"label.enable-share-url": "启用共享链接",
"label.invalid": "输入无效",
"label.invalid-domain": "无效域名",
"label.language": "Language",
"label.language": "语言",
"label.last-days": "最近 {x} 天",
"label.last-hours": "最近 {x} 小时",
"label.logged-in-as": "登录名: {username}",
@@ -37,7 +37,7 @@
"label.more": "更多",
"label.name": "名字",
"label.new-password": "新密码",
"label.owner": "Owner",
"label.owner": "所有者",
"label.password": "密码",
"label.passwords-dont-match": "密码不一致",
"label.profile": "个人资料",
@@ -46,12 +46,12 @@
"label.refresh": "刷新",
"label.required": "必填",
"label.reset": "重置",
"label.reset-website": "Reset statistics",
"label.reset-website": "重置统计数据",
"label.save": "保存",
"label.settings": "设置",
"label.share-url": "共享链接",
"label.single-day": "单日",
"label.theme": "Theme",
"label.theme": "主题",
"label.this-month": "本月",
"label.this-week": "本周",
"label.this-year": "今年",
@@ -64,7 +64,7 @@
"label.websites": "网站",
"message.active-users": "当前在线 {x} 人",
"message.confirm-delete": "你确定要删除 {target} 吗?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.confirm-reset": "您确定要重置 {target} 的数据吗?",
"message.copied": "复制成功!",
"message.delete-warning": "所有相关数据将会被删除。",
"message.failure": "出现错误。",
@@ -78,10 +78,10 @@
"message.no-websites-configured": "你还没有设置任何网站。",
"message.page-not-found": "网页未找到。",
"message.powered-by": "由 {name} 提供支持",
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
"message.reset-warning": "本网站的所有统计数据将被删除,但您的跟踪代码将保持不变。",
"message.save-success": "保存成功。",
"message.share-url": "这是 {target} 的共享链接。",
"message.toggle-charts": "Toggle charts",
"message.toggle-charts": "切换图表",
"message.track-stats": "把以下代码放到你的网站的 {head} 部分来收集 {target} 的数据。",
"message.type-delete": "在下方输入框输入 {delete} 以确认删除。",
"message.type-reset": "在下方输入框输入 {reset} 以确认删除。",
@@ -99,7 +99,7 @@
"metrics.filter.combined": "总和",
"metrics.filter.domain-only": "只看域名",
"metrics.filter.raw": "原始",
"metrics.languages": "Languages",
"metrics.languages": "语言",
"metrics.operating-systems": "操作系统",
"metrics.page-views": "页面浏览量",
"metrics.pages": "网页",
+9 -6
View File
@@ -1,5 +1,5 @@
import { BROWSERS } from './constants';
import { removeTrailingSlash, removeWWW } from './url';
import { removeTrailingSlash, removeWWW, getDomainName } from './url';
export const urlFilter = (data, { raw }) => {
const isValidUrl = url => {
@@ -46,11 +46,14 @@ export const urlFilter = (data, { raw }) => {
};
export const refFilter = (data, { domain, domainOnly, raw }) => {
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domain}`);
const domainName = getDomainName(domain);
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domainName}`);
const links = {};
const isValidRef = ref => {
return ref !== '' && ref !== null && !ref.startsWith('/') && !ref.startsWith('#');
const isValidRef = referrer => {
return (
referrer !== '' && referrer !== null && !referrer.startsWith('/') && !referrer.startsWith('#')
);
};
const cleanUrl = url => {
@@ -71,8 +74,8 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
if (protocol.startsWith('http')) {
const path = removeTrailingSlash(pathname);
const ref = searchParams.get('ref');
const query = ref ? `?ref=${ref}` : '';
const referrer = searchParams.get('referrer');
const query = referrer ? `?referrer=${referrer}` : '';
return removeTrailingSlash(`${removeWWW(hostname)}${path}`) + query;
}
+24 -8
View File
@@ -230,7 +230,16 @@ export async function saveEvent(website_id, session_id, url, event_type, event_v
}
export async function getAccounts() {
return runQuery(prisma.account.findMany());
return runQuery(
prisma.account.findMany({
orderBy: [
{ is_admin: 'desc' },
{
username: 'asc',
},
],
}),
);
}
export async function getAccountById(user_id) {
@@ -335,7 +344,7 @@ export async function getEvents(websites, start_at) {
export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at];
const { url, ref } = filters;
const { url, referrer } = filters;
let urlFilter = '';
let refFilter = '';
@@ -344,9 +353,9 @@ export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
params.push(decodeURIComponent(url));
}
if (ref) {
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
@@ -382,7 +391,7 @@ export function getPageviewStats(
filters = {},
) {
const params = [website_id, start_at, end_at];
const { url, ref } = filters;
const { url, referrer } = filters;
let urlFilter = '';
let refFilter = '';
@@ -392,9 +401,9 @@ export function getPageviewStats(
params.push(decodeURIComponent(url));
}
if (ref) {
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
@@ -444,10 +453,11 @@ export function getSessionMetrics(website_id, start_at, end_at, field, filters =
export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
const params = [website_id, start_at, end_at];
const { domain, url } = filters;
const { domain, url, referrer } = filters;
let domainFilter = '';
let urlFilter = '';
let refFilter = '';
if (domain) {
domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
@@ -459,6 +469,11 @@ export function getPageviewMetrics(website_id, start_at, end_at, field, table, f
params.push(decodeURIComponent(url));
}
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
`
select ${field} x, count(*) y
@@ -467,6 +482,7 @@ export function getPageviewMetrics(website_id, start_at, end_at, field, table, f
and created_at between $2 and $3
${domainFilter}
${urlFilter}
${refFilter}
group by 1
order by 2 desc
`,
+3 -1
View File
@@ -6,7 +6,9 @@ export function json(res, data = {}) {
return res.status(200).json(data);
}
export function send(res, data) {
export function send(res, data, type = 'text/plain') {
res.setHeader('Content-Type', type);
return res.status(200).send(data);
}
+2 -1
View File
@@ -9,7 +9,8 @@ export async function getSession(req) {
throw new Error('Invalid request');
}
const { website: website_uuid, hostname, screen, language, cache } = payload;
const { website: website_uuid, hostname, screen, language } = payload;
const cache = req.headers['x-umami-cache'];
if (cache) {
const result = await parseToken(cache);
+17 -18
View File
@@ -1,6 +1,6 @@
{
"name": "umami",
"version": "1.28.0",
"version": "1.29.0",
"description": "A simple, fast, website analytics alternative to Google Analytics.",
"author": "Mike Cao <mike@mikecao.com>",
"license": "MIT",
@@ -12,7 +12,8 @@
"scripts": {
"dev": "next dev",
"build": "npm-run-all build-tracker build-geo build-db build-app",
"start": "next start",
"start": "npm-run-all init start-app",
"start-app": "next start",
"start-env": "node -r dotenv/config scripts/start-env.js",
"build-app": "next build",
"build-tracker": "rollup -c rollup.tracker.config.js",
@@ -25,19 +26,20 @@
"build-mysql-client": "dotenv prisma generate -- --schema=./prisma/schema.mysql.prisma",
"build-postgresql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.postgresql.prisma",
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
"postbuild": "node scripts/postbuild.js",
"copy-db-schema": "node scripts/copy-db-schema.js",
"generate-lang": "npm-run-all extract-lang merge-lang",
"extract-lang": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json",
"merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js",
"compile-lang": "formatjs compile-folder --ast build public/messages",
"compile-lang": "formatjs compile-folder --ast build public/intl/messages",
"check-lang": "node scripts/check-lang.js",
"download-country-names": "node scripts/download-country-names.js",
"download-language-names": "node scripts/download-language-names.js",
"change-password": "node scripts/change-password.js",
"lint": "next lint --quiet",
"prepare": "husky install"
"prepare": "husky install",
"postbuild": "node scripts/postbuild.js",
"init": "node scripts/prestart.js"
},
"lint-staged": {
"**/*.js": [
@@ -54,8 +56,7 @@
},
"dependencies": {
"@fontsource/inter": "4.5.5",
"@prisma/client": "3.11.0",
"async-retry": "^1.3.3",
"@prisma/client": "3.11.1",
"bcryptjs": "^2.4.3",
"chalk": "^4.1.1",
"chart.js": "^2.9.4",
@@ -70,19 +71,16 @@
"fs-extra": "^10.0.1",
"immer": "^9.0.12",
"ipaddr.js": "^2.0.1",
"is-ci": "^3.0.1",
"is-docker": "^3.0.0",
"is-localhost-ip": "^1.4.0",
"isbot": "^3.4.5",
"jose": "2.0.5",
"maxmind": "^4.3.6",
"moment-timezone": "^0.5.33",
"next": "12.1.0",
"next": "12.1.4",
"node-fetch": "^3.2.3",
"prompts": "2.4.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-intl": "^5.24.7",
"react-simple-maps": "^2.3.0",
"react-spring": "^9.4.4",
@@ -100,17 +98,18 @@
"@formatjs/cli": "^4.2.29",
"@rollup/plugin-buble": "^0.21.3",
"@svgr/webpack": "^6.2.1",
"async-retry": "^1.3.3",
"cross-env": "^7.0.3",
"del": "^6.0.0",
"dotenv-cli": "^4.0.0",
"eslint": "^7.32.0",
"eslint-config-next": "^12.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"extract-react-intl-messages": "^4.1.1",
"husky": "^7.0.0",
"is-ci": "^3.0.1",
"is-docker": "^3.0.0",
"lint-staged": "^11.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.12",
@@ -119,8 +118,8 @@
"postcss-preset-env": "^7.4.2",
"postcss-rtlcss": "^3.5.3",
"prettier": "^2.6.0",
"prettier-eslint": "^13.0.0",
"prisma": "3.11.0",
"prisma": "3.11.1",
"prompts": "2.4.2",
"rollup": "^2.70.1",
"rollup-plugin-terser": "^7.0.2",
"stylelint": "^14.5.3",
+2 -5
View File
@@ -23,10 +23,7 @@ export default async (req, res) => {
// Only admin can change these fields
if (current_user_is_admin) {
// Cannot change username of admin
if (username !== 'admin') {
data.username = username;
}
data.username = username;
data.is_admin = is_admin;
}
@@ -37,7 +34,7 @@ export default async (req, res) => {
return badRequest(res, 'Account already exists');
}
}
console.log('------------------\n', data);
const updated = await updateAccount(user_id, data);
return ok(res, updated);
File renamed without changes.
+2 -1
View File
@@ -30,7 +30,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, type, start_at, end_at, url } = req.query;
const { id, type, start_at, end_at, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -75,6 +75,7 @@ export default async (req, res) => {
{
domain,
url: type !== 'url' && url,
referrer,
},
);
+3 -3
View File
@@ -11,7 +11,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, start_at, end_at, unit, tz, url, ref } = req.query;
const { id, start_at, end_at, unit, tz, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -22,10 +22,10 @@ export default async (req, res) => {
}
const [pageviews, sessions] = await Promise.all([
getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', { url, ref }),
getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', { url, referrer }),
getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', {
url,
ref,
referrer,
}),
]);
+6 -3
View File
@@ -8,7 +8,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, start_at, end_at, url, ref } = req.query;
const { id, start_at, end_at, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@@ -18,8 +18,11 @@ export default async (req, res) => {
const prevStartDate = new Date(+start_at - distance);
const prevEndDate = new Date(+end_at - distance);
const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url, ref });
const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url, ref });
const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url, referrer });
const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, {
url,
referrer,
});
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = {
File renamed without changes.
-1
View File
@@ -1 +0,0 @@
../seed.js
-1
View File
@@ -1 +0,0 @@
../seed.js
+1 -2
View File
@@ -8,13 +8,12 @@ const hashPassword = password => {
};
async function main() {
const password = hashPassword(process.env.ADMIN_PASSWORD || 'umami');
await prisma.account.upsert({
where: { username: 'admin' },
update: {},
create: {
username: 'admin',
password: password,
password: hashPassword(process.env.ADMIN_PASSWORD || 'umami'),
is_admin: true,
},
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more