diff --git a/README.md b/README.md index dd9e65ed..0898a8f3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,18 @@ # umami +Umami is a simple, fast, website analytics alternative to Google Analytics. + +## Getting started + +A detailed getting started guide can be found at [https://umami.is/docs/](https://umami.is/docs/) + ## Installation from source +### Requirements + +- A server with Node.js 10.13 or newer +- A database (MySQL or Postgresql) + ### Get the source code ``` @@ -37,6 +48,8 @@ For Postgresql: psql -h hostname -U username -d databasename -f sql/schema.postgresql.sql ``` +This will also create a login account with username **admin** and password **umami**. + ### Configure umami Create an `.env` file with the following diff --git a/components/charts/MetricCard.js b/components/charts/MetricCard.js index 4ebbe28a..ad6c62eb 100644 --- a/components/charts/MetricCard.js +++ b/components/charts/MetricCard.js @@ -1,12 +1,9 @@ import React from 'react'; import { useSpring, animated } from 'react-spring'; +import { formatNumber } from '../../lib/format'; import styles from './MetricCard.module.css'; -function defaultFormat(n) { - return Number(n).toFixed(0); -} - -const MetricCard = ({ value = 0, label, format = defaultFormat }) => { +const MetricCard = ({ value = 0, label, format = formatNumber }) => { const props = useSpring({ x: value, from: { x: 0 } }); return ( diff --git a/components/charts/MetricCard.module.css b/components/charts/MetricCard.module.css index 96adad3c..7f03049a 100644 --- a/components/charts/MetricCard.module.css +++ b/components/charts/MetricCard.module.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; justify-content: center; - width: 140px; + min-width: 140px; } .value { diff --git a/components/charts/MetricsBar.js b/components/charts/MetricsBar.js index 6a6c20a1..18a1a43b 100644 --- a/components/charts/MetricsBar.js +++ b/components/charts/MetricsBar.js @@ -2,13 +2,16 @@ import React, { useState, useEffect } from 'react'; import classNames from 'classnames'; import MetricCard from './MetricCard'; import { get } from 'lib/web'; -import { formatShortTime } from 'lib/format'; +import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format'; import styles from './MetricsBar.module.css'; export default function MetricsBar({ websiteId, startDate, endDate, className }) { const [data, setData] = useState({}); + const [format, setFormat] = useState(true); const { pageviews, uniques, bounces, totaltime } = data; + const formatFunc = format ? formatLongNumber : formatNumber; + async function loadData() { setData( await get(`/api/website/${websiteId}/metrics`, { @@ -18,14 +21,18 @@ export default function MetricsBar({ websiteId, startDate, endDate, className }) ); } + function handleSetFormat() { + setFormat(state => !state); + } + useEffect(() => { loadData(); }, [websiteId, startDate, endDate]); return ( -
- - +
+ + div:last-child { display: none; } diff --git a/components/charts/RankingsChart.js b/components/charts/RankingsChart.js index 54a7b4bb..be73be97 100644 --- a/components/charts/RankingsChart.js +++ b/components/charts/RankingsChart.js @@ -2,11 +2,11 @@ import React, { useState, useEffect, useMemo } from 'react'; import { FixedSizeList } from 'react-window'; import { useSpring, animated, config } from 'react-spring'; import classNames from 'classnames'; -import CheckVisible from 'components/helpers/CheckVisible'; import Button from 'components/common/Button'; import Arrow from 'assets/arrow-right.svg'; import { get } from 'lib/web'; import { percentFilter } from 'lib/filters'; +import { formatNumber, formatLongNumber } from 'lib/format'; import styles from './RankingsChart.module.css'; export default function RankingsChart({ @@ -23,6 +23,8 @@ export default function RankingsChart({ onExpand = () => {}, }) { const [data, setData] = useState(); + const [format, setFormat] = useState(true); + const formatFunc = format ? formatLongNumber : formatNumber; const rankings = useMemo(() => { if (data) { @@ -48,6 +50,19 @@ export default function RankingsChart({ onDataLoad(updated); } + function handleSetFormat() { + setFormat(state => !state); + } + + const Row = ({ index, style }) => { + const { x, y, z } = rankings[index]; + return ( +
+ +
+ ); + }; + useEffect(() => { if (websiteId) { loadData(); @@ -58,25 +73,23 @@ export default function RankingsChart({ return null; } - const Row = ({ index, style }) => { - const { x, y, z } = rankings[index]; - return ( -
- -
- ); - }; - return (
-
+
{title}
{heading}
{limit ? ( rankings.map(({ x, y, z }) => ( - + )) ) : ( @@ -95,7 +108,7 @@ export default function RankingsChart({ ); } -const AnimatedRow = ({ label, value, percent, animate }) => { +const AnimatedRow = ({ label, value = 0, percent, animate, format }) => { const props = useSpring({ width: percent, y: value, @@ -106,7 +119,7 @@ const AnimatedRow = ({ label, value, percent, animate }) => { return (
{label}
- {props.y.interpolate(n => n.toFixed(0))} + {props.y?.interpolate(format)}
{title &&
{title}
}
{children}
-
+ , + document.getElementById('__modals'), ); } diff --git a/components/forms/LoginForm.js b/components/forms/LoginForm.js index 00f45a37..78e0480e 100644 --- a/components/forms/LoginForm.js +++ b/components/forms/LoginForm.js @@ -32,10 +32,10 @@ export default function LoginForm() { const handleSubmit = async ({ username, password }) => { const response = await post('/api/auth/login', { username, password }); - if (response?.token) { + if (typeof response !== 'string') { await Router.push('/'); } else { - setMessage('Incorrect username/password'); + setMessage(response.startsWith('401') ? 'Incorrect username/password' : response); } }; diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js index 86117d15..e892359c 100644 --- a/components/forms/TrackingCodeForm.js +++ b/components/forms/TrackingCodeForm.js @@ -18,7 +18,7 @@ export default function TrackingCodeForm({ values, onClose }) { rows={3} cols={60} spellCheck={false} - defaultValue={``} readOnly /> diff --git a/components/layout/Layout.js b/components/layout/Layout.js index 1285f2c3..021745cc 100644 --- a/components/layout/Layout.js +++ b/components/layout/Layout.js @@ -16,6 +16,7 @@ export default function Layout({ title, children, header = true, footer = true } {header &&
}
{children}
+
{footer &&