Merge branch 'dev' into analytics

This commit is contained in:
Mike Cao 2023-08-30 16:33:31 -07:00
commit 6108e7cbc2
13 changed files with 125 additions and 93 deletions

View File

@ -10,7 +10,7 @@ A detailed getting started guide can be found at [https://umami.is/docs/](https:
### Requirements
- A server with Node.js version 12 or newer
- A server with Node.js version 16.13 or newer
- A database. Umami supports [MySQL](https://www.mysql.com/) and [Postgresql](https://www.postgresql.org/) databases.
### Install Yarn

View File

@ -1,6 +1,6 @@
{
"name": "umami",
"version": "2.6.0",
"version": "2.6.1",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao <mike@mikecao.com>",
"license": "MIT",

View File

@ -104,7 +104,7 @@
"label.browser": [
{
"type": 0,
"value": "Browser"
"value": "浏览器"
}
],
"label.browsers": [
@ -134,7 +134,7 @@
"label.city": [
{
"type": 0,
"value": "City"
"value": "市/县"
}
],
"label.clear-all": [
@ -176,7 +176,7 @@
"label.country": [
{
"type": 0,
"value": "Country"
"value": "国家/地区"
}
],
"label.create-report": [
@ -230,7 +230,7 @@
"label.date": [
{
"type": 0,
"value": "Date"
"value": "日期"
}
],
"label.date-range": [
@ -242,7 +242,7 @@
"label.day": [
{
"type": 0,
"value": "Day"
"value": ""
}
],
"label.default-date-range": [
@ -296,7 +296,7 @@
"label.device": [
{
"type": 0,
"value": "Device"
"value": "设备"
}
],
"label.devices": [
@ -440,13 +440,13 @@
"label.is-not-set": [
{
"type": 0,
"value": "Is not set"
"value": "未设置"
}
],
"label.is-set": [
{
"type": 0,
"value": "Is set"
"value": "已设置"
}
],
"label.join": [
@ -576,7 +576,7 @@
"label.my-websites": [
{
"type": 0,
"value": "My websites"
"value": "我的网站"
}
],
"label.name": [
@ -618,7 +618,15 @@
"label.page-of": [
{
"type": 0,
"value": "Page "
"value": "总"
},
{
"type": 1,
"value": "total"
},
{
"type": 0,
"value": "中的第"
},
{
"type": 1,
@ -626,11 +634,7 @@
},
{
"type": 0,
"value": " of "
},
{
"type": 1,
"value": "total"
"value": "页"
}
],
"label.page-views": [
@ -642,7 +646,7 @@
"label.pageTitle": [
{
"type": 0,
"value": "Page title"
"value": "标题"
}
],
"label.pages": [
@ -704,7 +708,7 @@
"label.referrer": [
{
"type": 0,
"value": "Referrer"
"value": "来源"
}
],
"label.referrers": [
@ -728,7 +732,7 @@
"label.region": [
{
"type": 0,
"value": "Region"
"value": "州/省"
}
],
"label.regions": [
@ -770,7 +774,7 @@
"label.retention": [
{
"type": 0,
"value": "Retention"
"value": "保留"
}
],
"label.role": [
@ -872,7 +876,7 @@
"label.team-name": [
{
"type": 0,
"value": "Team name"
"value": "团队名称"
}
],
"label.team-owner": [
@ -884,7 +888,7 @@
"label.team-websites": [
{
"type": 0,
"value": "Team websites"
"value": "团队网站"
}
],
"label.teams": [
@ -974,7 +978,7 @@
"label.unique": [
{
"type": 0,
"value": "Unique"
"value": "独立"
}
],
"label.unique-visitors": [
@ -998,13 +1002,13 @@
"label.url": [
{
"type": 0,
"value": "URL"
"value": "网址"
}
],
"label.urls": [
{
"type": 0,
"value": "URLs"
"value": "网址"
}
],
"label.user": [
@ -1046,7 +1050,7 @@
"label.view-only": [
{
"type": 0,
"value": "View only"
"value": "仅浏览量"
}
],
"label.views": [
@ -1190,15 +1194,15 @@
"message.event-log": [
{
"type": 1,
"value": "event"
"value": "url"
},
{
"type": 0,
"value": " on "
"value": "上的"
},
{
"type": 1,
"value": "url"
"value": "event"
}
],
"message.go-to-settings": [

View File

@ -1,8 +1,8 @@
import { useState } from 'react';
import { Button, Icon, Icons, Text, Flexbox } from 'react-basics';
import { Button, Icon, Icons, Text } from 'react-basics';
import Link from 'next/link';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import Pager from 'components/common/Pager';
import WebsiteChartList from 'components/pages/websites/WebsiteChartList';
import DashboardSettingsButton from 'components/pages/dashboard/DashboardSettingsButton';
import DashboardEdit from 'components/pages/dashboard/DashboardEdit';
@ -11,23 +11,24 @@ import useApi from 'components/hooks/useApi';
import useDashboard from 'store/dashboard';
import useMessages from 'components/hooks/useMessages';
import useLocale from 'components/hooks/useLocale';
import useApiFilter from 'components/hooks/useApiFilter';
export function Dashboard() {
const { formatMessage, labels, messages } = useMessages();
const dashboard = useDashboard();
const { showCharts, limit, editing } = dashboard;
const [max, setMax] = useState(limit);
const { get, useQuery } = useApi();
const { data, isLoading, error } = useQuery(['websites'], () =>
get('/websites', { includeTeams: 1 }),
);
const hasData = data && data?.data.length !== 0;
const { showCharts, editing } = useDashboard();
const { dir } = useLocale();
function handleMore() {
setMax(max + limit);
}
const { get, useQuery } = useApi();
const { page, handlePageChange } = useApiFilter();
const pageSize = 10;
const {
data: result,
isLoading,
error,
} = useQuery(['websites', page, pageSize], () =>
get('/websites', { includeTeams: 1, page, pageSize }),
);
const { data, count } = result || {};
const hasData = data && data?.length !== 0;
return (
<Page loading={isLoading} error={error}>
@ -48,19 +49,17 @@ export function Dashboard() {
)}
{hasData && (
<>
{editing && <DashboardEdit websites={data?.data} />}
{editing && <DashboardEdit />}
{!editing && (
<WebsiteChartList websites={data?.data} showCharts={showCharts} limit={max} />
)}
{max < data.length && (
<Flexbox justifyContent="center">
<Button onClick={handleMore}>
<Icon rotate={dir === 'rtl' ? 180 : 0}>
<Icons.More />
</Icon>
<Text>{formatMessage(labels.more)}</Text>
</Button>
</Flexbox>
<>
<WebsiteChartList websites={data} showCharts={showCharts} limit={pageSize} />
<Pager
page={page}
pageSize={pageSize}
count={count}
onPageChange={handlePageChange}
/>
</>
)}
</>
)}

View File

@ -5,23 +5,33 @@ import { Button } from 'react-basics';
import { firstBy } from 'thenby';
import useDashboard, { saveDashboard } from 'store/dashboard';
import useMessages from 'components/hooks/useMessages';
import useApi from 'components/hooks/useApi';
import styles from './DashboardEdit.module.css';
import Page from 'components/layout/Page';
const dragId = 'dashboard-website-ordering';
export function DashboardEdit({ websites }) {
export function DashboardEdit() {
const settings = useDashboard();
const { websiteOrder } = settings;
const { formatMessage, labels } = useMessages();
const [order, setOrder] = useState(websiteOrder || []);
const { get, useQuery } = useApi();
const {
data: result,
isLoading,
error,
} = useQuery(['websites'], () => get('/websites', { includeTeams: 1 }));
const { data: websites } = result || {};
const ordered = useMemo(
() =>
websites
const ordered = useMemo(() => {
if (websites) {
return websites
.map(website => ({ ...website, order: order.indexOf(website.id) }))
.sort(firstBy('order')),
[websites, order],
);
.sort(firstBy('order'));
}
return [];
}, [websites, order]);
function handleWebsiteDrag({ destination, source }) {
if (!destination || destination.index === source.index) return;
@ -49,7 +59,7 @@ export function DashboardEdit({ websites }) {
}
return (
<>
<Page loading={isLoading} error={error}>
<div className={styles.buttons}>
<Button onClick={handleSave} variant="action" size="small">
{formatMessage(labels.save)}
@ -95,7 +105,7 @@ export function DashboardEdit({ websites }) {
</Droppable>
</DragDropContext>
</div>
</>
</Page>
);
}

View File

@ -16,19 +16,19 @@
"label.before": "之前",
"label.bounce-rate": "跳出率",
"label.breakdown": "故障",
"label.browser": "Browser",
"label.browser": "浏览器",
"label.browsers": "浏览器",
"label.cancel": "取消",
"label.change-password": "更新密码",
"label.cities": "市/县",
"label.city": "City",
"label.city": "市/县",
"label.clear-all": "清除全部",
"label.confirm": "确认",
"label.confirm-password": "确认密码",
"label.contains": "包含",
"label.continue": "继续",
"label.countries": "国家/地区",
"label.country": "Country",
"label.country": "国家/地区",
"label.create-report": "创建报告",
"label.create-team": "创建团队",
"label.create-user": "创建用户",
@ -37,9 +37,9 @@
"label.custom-range": "自定义时间段",
"label.dashboard": "仪表板",
"label.data": "统计数据",
"label.date": "Date",
"label.date": "日期",
"label.date-range": "时间段",
"label.day": "Day",
"label.day": "",
"label.default-date-range": "默认时间段",
"label.delete": "删除",
"label.delete-team": "删除团队",
@ -48,7 +48,7 @@
"label.description": "描述",
"label.desktop": "台式机",
"label.details": "详细信息",
"label.device": "Device",
"label.device": "设备",
"label.devices": "设备",
"label.dismiss": "关闭",
"label.does-not-contain": "不包含",
@ -72,8 +72,8 @@
"label.insights": "见解",
"label.is": "等于",
"label.is-not": "不等于",
"label.is-not-set": "Is not set",
"label.is-set": "Is set",
"label.is-not-set": "未设置",
"label.is-set": "已设置",
"label.join": "加入",
"label.join-team": "加入团队",
"label.language": "语言",
@ -92,16 +92,16 @@
"label.min": "最小",
"label.mobile": "手机",
"label.more": "更多",
"label.my-websites": "My websites",
"label.my-websites": "我的网站",
"label.name": "名字",
"label.new-password": "新密码",
"label.none": "无",
"label.os": "OS",
"label.overview": "概览",
"label.owner": "所有者",
"label.page-of": "Page {current} of {total}",
"label.page-of": "总{total}中的第{current}页",
"label.page-views": "页面浏览量",
"label.pageTitle": "Page title",
"label.pageTitle": "标题",
"label.pages": "网页",
"label.password": "密码",
"label.powered-by": "由 {name} 提供支持",
@ -110,18 +110,18 @@
"label.query": "查询",
"label.query-parameters": "查询参数",
"label.realtime": "实时",
"label.referrer": "Referrer",
"label.referrer": "来源",
"label.referrers": "来源域名",
"label.refresh": "刷新",
"label.regenerate": "重新生成",
"label.region": "Region",
"label.region": "州/省",
"label.regions": "州/省",
"label.remove": "移除",
"label.reports": "报告",
"label.required": "必填",
"label.reset": "重置",
"label.reset-website": "重置统计数据",
"label.retention": "Retention",
"label.retention": "保留",
"label.role": "角色",
"label.run-query": "查询",
"label.save": "保存",
@ -138,9 +138,9 @@
"label.team-guest": "团队访客",
"label.team-id": "团队 ID",
"label.team-member": "团队成员",
"label.team-name": "Team name",
"label.team-name": "团队名称",
"label.team-owner": "团队所有者",
"label.team-websites": "Team websites",
"label.team-websites": "团队网站",
"label.teams": "团队",
"label.theme": "主题",
"label.this-month": "本月",
@ -155,19 +155,19 @@
"label.tracking-code": "跟踪代码",
"label.true": "是",
"label.type": "类型",
"label.unique": "Unique",
"label.unique": "独立",
"label.unique-visitors": "独立访客",
"label.unknown": "未知",
"label.untitled": "未命名",
"label.url": "URL",
"label.urls": "URLs",
"label.url": "网址",
"label.urls": "网址",
"label.user": "用户",
"label.username": "用户名",
"label.users": "用户",
"label.value": "值",
"label.view": "查看",
"label.view-details": "查看更多",
"label.view-only": "View only",
"label.view-only": "仅浏览量",
"label.views": "浏览量",
"label.visitors": "访客",
"label.website": "网站",
@ -183,7 +183,7 @@
"message.delete-website": "确定删除该网站, 请在下面的输入框中输入 {confirmation} 进行二次确认。",
"message.delete-website-warning": "所有相关数据将会被删除。",
"message.error": "出现错误。",
"message.event-log": "{event} on {url}",
"message.event-log": "{url}上的{event}",
"message.go-to-settings": "去设置",
"message.incorrect-username-password": "用户名或密码不正确。",
"message.invalid-domain": "无效域名",

View File

@ -57,6 +57,14 @@ export function getDevice(screen, os) {
}
}
function getRegionCode(country, region) {
if (!country || !region) {
return undefined;
}
return region.includes('-') ? region : `${country}-${region}`;
}
export async function getLocation(ip, req) {
// Ignore local ips
if (await isLocalhost(ip)) {
@ -71,7 +79,7 @@ export async function getLocation(ip, req) {
return {
country,
subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`,
subdivision1: getRegionCode(country, subdivision1),
city,
};
}
@ -84,7 +92,7 @@ export async function getLocation(ip, req) {
return {
country,
subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`,
subdivision1: getRegionCode(country, subdivision1),
city,
};
}

View File

@ -23,7 +23,6 @@ export interface UserPasswordRequestBody {
const schema = {
POST: yup.object().shape({
id: yup.string().uuid().required(),
currentPassword: yup.string().required(),
newPassword: yup.string().min(8).required(),
}),

View File

@ -79,6 +79,10 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
const { type, payload } = getJsonBody<CollectRequestBody>(req);
if (!type || !payload) {
return badRequest(res);
}
req.yup = schema;
await useValidate(req, res);

View File

@ -42,8 +42,13 @@ export default async (
} = req.auth;
if (req.method === 'GET') {
req.query.id = userId;
req.query.pageSize = 100;
if (!req.query.id) {
req.query.id = userId;
}
if (!req.query.pageSize) {
req.query.pageSize = 100;
}
return userWebsites(req as any, res);
}

View File

@ -142,6 +142,7 @@ export async function getReports(
...pageFilters,
...(options?.include && { include: options.include }),
});
const count = await prisma.client.report.count({
where,
});

View File

@ -135,6 +135,7 @@ export async function getTeams(
...pageFilters,
...(options?.include && { include: options?.include }),
});
const count = await prisma.client.team.count({ where });
return { data: teams, count, ...getParameters };

View File

@ -107,7 +107,8 @@ export async function getWebsites(
...pageFilters,
...(options?.include && { include: options.include }),
});
const count = await prisma.client.website.count({ where });
const count = await prisma.client.website.count({ where: { ...where, deletedAt: null } });
return { data: websites, count, ...getParameters };
}