diff --git a/.eslintrc.json b/.eslintrc.json index 7a824ff6c..9d747b879 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,14 +4,6 @@ "es2020": true, "node": true }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 11, - "sourceType": "module" - }, "extends": [ "eslint:recommended", "plugin:prettier/recommended", @@ -19,22 +11,29 @@ "plugin:@typescript-eslint/recommended", "next" ], - + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 11, + "sourceType": "module" + }, "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/resolver": { "alias": { "map": [ - ["assets", "./assets"], - ["components", "./components"], + ["assets", "./src/assets"], + ["components", "./src/components"], ["db", "./db"], - ["hooks", "./hooks"], - ["lang", "./lang"], - ["lib", "./lib"], + ["hooks", "./src/components/hooks"], + ["lang", "./src/lang"], + ["lib", "./src/lib"], ["public", "./public"], - ["queries", "./queries"], - ["store", "./store"], - ["styles", "./styles"] + ["queries", "./src/queries"], + ["store", "./src/store"], + ["styles", "./src/styles"] ], "extensions": [".ts", ".tsx", ".js", ".jsx", ".json"] } @@ -50,7 +49,9 @@ "@next/next/no-img-element": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-var-requires": "off" + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }] }, "globals": { "React": "writable" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 775f9ecf5..66e16a03e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,6 @@ jobs: strategy: matrix: include: - - node-version: 16.x - db-type: postgresql - - node-version: 16.x - db-type: mysql - node-version: 18.x db-type: postgresql - node-version: 18.x diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index bf2505b14..f16040149 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -19,4 +19,7 @@ jobs: close-issue-message: 'This issue was closed because it has been inactive for 7 days since being marked as stale.' days-before-pr-stale: -1 days-before-pr-close: -1 + operations-per-run: 200 + ascending: true repo-token: ${{ secrets.GITHUB_TOKEN }} + exempt-issue-labels: bug,enhancement diff --git a/.gitignore b/.gitignore index 99087ab50..050397c90 100644 --- a/.gitignore +++ b/.gitignore @@ -34,9 +34,7 @@ yarn-error.log* # local env files .env -.env.development.local -.env.test.local -.env.production.local +.env.* *.dev.yml diff --git a/Dockerfile b/Dockerfile index bdc678dac..801b2bc20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,8 @@ RUN yarn install --frozen-lockfile FROM node:18-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules -COPY docker/middleware.js . COPY . . +COPY docker/middleware.js ./src ARG DATABASE_TYPE ARG BASE_PATH @@ -35,7 +35,9 @@ ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -RUN yarn add npm-run-all dotenv prisma +RUN set -x \ + && apk add --no-cache curl \ + && yarn add npm-run-all dotenv prisma semver # You only need to copy next.config.js if you are NOT using the default configuration COPY --from=builder /app/next.config.js . @@ -53,6 +55,7 @@ USER nextjs EXPOSE 3000 +ENV HOSTNAME 0.0.0.0 ENV PORT 3000 CMD ["yarn", "start-docker"] diff --git a/README.md b/README.md index 19935ed59..32e78e31c 100644 --- a/README.md +++ b/README.md @@ -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 @@ -72,13 +72,13 @@ docker compose up -d Alternatively, to pull just the Umami Docker image with PostgreSQL support: ```bash -docker pull docker.umami.dev/umami-software/umami:postgresql-latest +docker pull ghcr.io/umami-software/umami:postgresql-latest ``` Or with MySQL support: ```bash -docker pull docker.umami.dev/umami-software/umami:mysql-latest +docker pull ghcr.io/umami-software/umami:mysql-latest ``` ## Getting updates diff --git a/components/common/FilterButtons.js b/components/common/FilterButtons.js deleted file mode 100644 index f5a54fb63..000000000 --- a/components/common/FilterButtons.js +++ /dev/null @@ -1,13 +0,0 @@ -import { ButtonGroup, Button, Flexbox } from 'react-basics'; - -export function FilterButtons({ items, selectedKey, onSelect }) { - return ( - - - {({ key, label }) => } - - - ); -} - -export default FilterButtons; diff --git a/components/common/HamburgerButton.js b/components/common/HamburgerButton.js deleted file mode 100644 index 48c807708..000000000 --- a/components/common/HamburgerButton.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Button, Icon } from 'react-basics'; -import { useState } from 'react'; -import MobileMenu from './MobileMenu'; -import Icons from 'components/icons'; -import useMessages from 'hooks/useMessages'; -import useConfig from 'hooks/useConfig'; - -export function HamburgerButton() { - const { formatMessage, labels } = useMessages(); - const [active, setActive] = useState(false); - const { cloudMode } = useConfig(); - - const menuItems = [ - { - label: formatMessage(labels.dashboard), - url: '/dashboard', - }, - !cloudMode && { - label: formatMessage(labels.settings), - url: '/settings', - children: [ - { - label: formatMessage(labels.websites), - url: '/settings/websites', - }, - { - label: formatMessage(labels.teams), - url: '/settings/teams', - }, - { - label: formatMessage(labels.users), - url: '/settings/users', - }, - { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - ], - }, - cloudMode && { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - !cloudMode && { label: formatMessage(labels.logout), url: '/logout' }, - ].filter(n => n); - - const handleClick = () => setActive(state => !state); - const handleClose = () => setActive(false); - - return ( - <> - - {active && } - - ); -} - -export default HamburgerButton; diff --git a/components/common/LinkButton.js b/components/common/LinkButton.js deleted file mode 100644 index 8c0501475..000000000 --- a/components/common/LinkButton.js +++ /dev/null @@ -1,12 +0,0 @@ -import Link from 'next/link'; -import { Icon, Icons, Text } from 'react-basics'; -import styles from './LinkButton.module.css'; - -export default function LinkButton({ href, icon, children }) { - return ( - - {icon || } - {children} - - ); -} diff --git a/components/common/LinkButton.module.css b/components/common/LinkButton.module.css deleted file mode 100644 index ae8a3b627..000000000 --- a/components/common/LinkButton.module.css +++ /dev/null @@ -1,28 +0,0 @@ -.button { - display: flex; - align-items: center; - align-self: flex-start; - white-space: nowrap; - gap: var(--size200); - font-family: inherit; - color: var(--base900); - background: var(--base100); - border: 1px solid transparent; - border-radius: var(--border-radius); - min-height: var(--base-height); - padding: 0 var(--size600); - position: relative; - cursor: pointer; -} - -.button:hover { - background: var(--base200); -} - -.button:active { - background: var(--base300); -} - -.button:visited { - color: var(--base900); -} diff --git a/components/common/SettingsTable.js b/components/common/SettingsTable.js deleted file mode 100644 index 8f0398584..000000000 --- a/components/common/SettingsTable.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Table, TableHeader, TableBody, TableRow, TableCell, TableColumn } from 'react-basics'; -import styles from './SettingsTable.module.css'; - -export function SettingsTable({ columns = [], data = [], children, cellRender }) { - return ( - - - {(column, index) => { - return ( - - {column.label} - - ); - }} - - - {(row, keys, rowIndex) => { - row.action = children(row, keys, rowIndex); - - return ( - - {(data, key, colIndex) => { - return ( - - - {cellRender ? cellRender(row, data, key, colIndex) : data[key]} - - ); - }} - - ); - }} - -
- ); -} - -export default SettingsTable; diff --git a/components/common/SettingsTable.module.css b/components/common/SettingsTable.module.css deleted file mode 100644 index fd6cddfad..000000000 --- a/components/common/SettingsTable.module.css +++ /dev/null @@ -1,44 +0,0 @@ -.cell { - align-items: center; -} - -.row .cell:last-child { - gap: 10px; - justify-content: flex-end; -} - -.label { - display: none; - font-weight: 700; -} - -@media screen and (max-width: 992px) { - .header .cell { - display: none; - } - - .label { - display: block; - min-width: 100px; - } - - .row .cell { - padding-left: 0; - flex-basis: 100%; - } -} - -@media screen and (max-width: 1200px) { - .row { - flex-wrap: wrap; - } - - .header .cell:last-child { - display: none; - } - - .row .cell:last-child { - padding-left: 0; - flex-basis: 100%; - } -} diff --git a/components/input/WebsiteDateFilter.js b/components/input/WebsiteDateFilter.js deleted file mode 100644 index 47e6f0160..000000000 --- a/components/input/WebsiteDateFilter.js +++ /dev/null @@ -1,23 +0,0 @@ -import useDateRange from 'hooks/useDateRange'; -import DateFilter from './DateFilter'; -import styles from './WebsiteDateFilter.module.css'; - -export default function WebsiteDateFilter({ websiteId }) { - const [dateRange, setDateRange] = useDateRange(websiteId); - const { value, startDate, endDate } = dateRange; - - const handleChange = async value => { - setDateRange(value); - }; - - return ( - - ); -} diff --git a/components/input/WebsiteDateFilter.module.css b/components/input/WebsiteDateFilter.module.css deleted file mode 100644 index 13234c55c..000000000 --- a/components/input/WebsiteDateFilter.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.dropdown { - min-width: 200px; -} diff --git a/components/input/WebsiteSelect.js b/components/input/WebsiteSelect.js deleted file mode 100644 index b77ae57c8..000000000 --- a/components/input/WebsiteSelect.js +++ /dev/null @@ -1,28 +0,0 @@ -import { Dropdown, Item } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; - -export function WebsiteSelect({ websiteId, onSelect }) { - const { formatMessage, labels } = useMessages(); - const { get, useQuery } = useApi(); - const { data } = useQuery(['websites:me'], () => get('/me/websites')); - - const renderValue = value => { - return data?.find(({ id }) => id === value)?.name; - }; - - return ( - - {({ id, name }) => {name}} - - ); -} - -export default WebsiteSelect; diff --git a/components/layout/AppLayout.js b/components/layout/AppLayout.js deleted file mode 100644 index 989128f9e..000000000 --- a/components/layout/AppLayout.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Container } from 'react-basics'; -import Head from 'next/head'; -import NavBar from 'components/layout/NavBar'; -import UpdateNotice from 'components/common/UpdateNotice'; -import useRequireLogin from 'hooks/useRequireLogin'; -import useConfig from 'hooks/useConfig'; -import { CURRENT_VERSION } from 'lib/constants'; -import styles from './AppLayout.module.css'; - -export function AppLayout({ title, children }) { - const { user } = useRequireLogin(); - const config = useConfig(); - - if (!user || !config) { - return null; - } - - return ( -
- - - {title ? `${title} | umami` : 'umami'} - - -
- {children} -
-
- ); -} - -export default AppLayout; diff --git a/components/layout/Grid.js b/components/layout/Grid.js deleted file mode 100644 index 0276063b5..000000000 --- a/components/layout/Grid.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Row, Column } from 'react-basics'; -import classNames from 'classnames'; -import styles from './Grid.module.css'; - -export function GridRow(props) { - const { className, ...otherProps } = props; - return ; -} - -export function GridColumn(props) { - const { className, ...otherProps } = props; - return ; -} diff --git a/components/layout/Grid.module.css b/components/layout/Grid.module.css deleted file mode 100644 index dc2e8ff6c..000000000 --- a/components/layout/Grid.module.css +++ /dev/null @@ -1,36 +0,0 @@ -.col { - display: flex; - flex-direction: column; - padding: 20px; -} - -.row { - border-top: 1px solid var(--base300); - min-height: 430px; -} - -.row > .col { - border-inline-start: 1px solid var(--base300); -} - -.row > .col:first-child { - border-inline-start: 0; - padding-inline-start: 0; -} - -.row > .col:last-child { - padding-inline-end: 0; -} - -@media only screen and (max-width: 992px) { - .row { - border: 0; - } - - .row > .col { - border-top: 1px solid var(--base300); - border-inline-start: 0; - border-inline-end: 0; - padding: 20px 0; - } -} diff --git a/components/layout/Header.js b/components/layout/Header.js deleted file mode 100644 index 21cdd2513..000000000 --- a/components/layout/Header.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Column, Icon, Row, Text } from 'react-basics'; -import Link from 'next/link'; -import LanguageButton from 'components/input/LanguageButton'; -import ThemeButton from 'components/input/ThemeButton'; -import SettingsButton from 'components/input/SettingsButton'; -import Icons from 'components/icons'; -import styles from './Header.module.css'; - -export function Header() { - return ( -
- - - - - - - umami - - - - - - - - -
- ); -} - -export default Header; diff --git a/components/layout/NavBar.js b/components/layout/NavBar.js deleted file mode 100644 index 97eaa46c3..000000000 --- a/components/layout/NavBar.js +++ /dev/null @@ -1,63 +0,0 @@ -import { Icon, Text, Row, Column } from 'react-basics'; -import Link from 'next/link'; -import classNames from 'classnames'; -import Icons from 'components/icons'; -import ThemeButton from 'components/input/ThemeButton'; -import LanguageButton from 'components/input/LanguageButton'; -import ProfileButton from 'components/input/ProfileButton'; -import styles from './NavBar.module.css'; -import useConfig from 'hooks/useConfig'; -import useMessages from 'hooks/useMessages'; -import { useRouter } from 'next/router'; -import HamburgerButton from '../common/HamburgerButton'; - -export function NavBar() { - const { pathname } = useRouter(); - const { cloudMode } = useConfig(); - const { formatMessage, labels } = useMessages(); - - const links = [ - { label: formatMessage(labels.dashboard), url: '/dashboard' }, - !cloudMode && { label: formatMessage(labels.settings), url: '/settings' }, - ].filter(n => n); - - return ( -
- - -
- - - - umami -
-
- {links.map(({ url, label }) => { - return ( - - {label} - - ); - })} -
-
- -
- - - -
-
- -
-
-
-
- ); -} - -export default NavBar; diff --git a/components/layout/Page.module.css b/components/layout/Page.module.css deleted file mode 100644 index c546971b6..000000000 --- a/components/layout/Page.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.page { - flex: 1; - display: flex; - flex-direction: column; - background: var(--base50); - position: relative; -} diff --git a/components/layout/ReportsLayout.js b/components/layout/ReportsLayout.js deleted file mode 100644 index fd63a67e7..000000000 --- a/components/layout/ReportsLayout.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Column, Row } from 'react-basics'; -import styles from './ReportsLayout.module.css'; - -export function SettingsLayout({ children, filter, header }) { - return ( - <> - {header} - - {filter && ( - -

Filters

- {filter} -
- )} - - {children} - -
- - ); -} - -export default SettingsLayout; diff --git a/components/layout/ReportsLayout.module.css b/components/layout/ReportsLayout.module.css deleted file mode 100644 index 6922665fa..000000000 --- a/components/layout/ReportsLayout.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.filter { - margin-top: 30px; - min-width: 200px; - max-width: 100vw; - padding: 10px; - background: var(--base50); - border-radius: 5px; - border: 1px solid var(--border-color); -} - -.filter h2 { - padding-bottom: 20px; -} - -.content { - min-height: 50vh; -} - -@media only screen and (max-width: 768px) { - .menu { - display: none; - } -} diff --git a/components/layout/SettingsLayout.module.css b/components/layout/SettingsLayout.module.css deleted file mode 100644 index 569b903ba..000000000 --- a/components/layout/SettingsLayout.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.menu { - display: flex; - flex-direction: column; - padding-top: 40px; -} - -.content { - min-height: 50vh; -} - -@media only screen and (max-width: 768px) { - .menu { - display: none; - } -} diff --git a/components/layout/ShareLayout.js b/components/layout/ShareLayout.js deleted file mode 100644 index c634e1b6d..000000000 --- a/components/layout/ShareLayout.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Container } from 'react-basics'; -import Header from './Header'; -import Footer from './Footer'; - -export function ShareLayout({ children }) { - return ( - -
-
{children}
-