diff --git a/.github/workflows/cd-manual.yml b/.github/workflows/cd-manual.yml new file mode 100644 index 00000000..757ae90f --- /dev/null +++ b/.github/workflows/cd-manual.yml @@ -0,0 +1,36 @@ +name: Create docker images + +on: + workflow_dispatch: + inputs: + version: + type: string + description: Version + required: true + add-latest: + type: boolean + description: Add latest tag + required: false + +jobs: + build: + name: Build, push, and deploy + runs-on: ubuntu-latest + + strategy: + matrix: + db-type: [postgresql, mysql] + + steps: + - uses: actions/checkout@v2 + + - uses: mr-smithers-excellent/docker-build-push@v5 + name: Build & push Docker image for ${{ matrix.db-type }} + with: + image: umami + tags: ${{ matrix.db-type }}-${{ inputs.version }} + addLatest: ${{ inputs.add-latest }} + buildArgs: DATABASE_TYPE=${{ matrix.db-type }} + registry: ghcr.io/${{ github.actor }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 86f08a04..229b295b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,16 +1,12 @@ name: Create docker images -on: - workflow_dispatch: - inputs: - version: - type: string - description: Version - required: true +on: [create] jobs: + build: name: Build, push, and deploy + if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest strategy: @@ -20,11 +16,14 @@ jobs: 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 }}-${{ inputs.version }} + 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 }} diff --git a/.gitignore b/.gitignore index e6b35441..6414cc5f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ # next.js /.next/ /out/ -/prisma/schema.prisma +/prisma/ # production /build diff --git a/Dockerfile b/Dockerfile index 84752e17..31dfe7a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,18 +8,20 @@ RUN yarn install --frozen-lockfile # Rebuild the source code only when needed FROM node:16-alpine AS builder -ARG BASE_PATH -ARG DATABASE_TYPE -ENV BASE_PATH=$BASE_PATH -ENV DATABASE_URL "postgresql://umami:umami@db:5432/umami" -ENV DATABASE_TYPE=$DATABASE_TYPE WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. +ARG DATABASE_URL +ARG DATABASE_TYPE +ARG BASE_PATH +ARG DISABLE_LOGIN + +ENV DATABASE_URL $DATABASE_URL +ENV DATABASE_TYPE $DATABASE_TYPE +ENV BASE_PATH $BASE_PATH +ENV DISABLE_LOGIN $DISABLE_LOGIN + ENV NEXT_TELEMETRY_DISABLED 1 RUN yarn build @@ -29,16 +31,19 @@ FROM node:16-alpine AS runner WORKDIR /app ENV NODE_ENV production -# Uncomment the following line in case you want to disable telemetry during runtime. ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs +RUN yarn global add prisma + # You only need to copy next.config.js if you are NOT using the default configuration COPY --from=builder /app/next.config.js ./ COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/prisma/schema.prisma ./prisma/schema.prisma +COPY --from=builder /app/prisma/migrations ./prisma/migrations # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing @@ -51,4 +56,4 @@ EXPOSE 3000 ENV PORT 3000 -CMD ["node", "server.js"] +CMD ["yarn", "start-docker"] diff --git a/README.md b/README.md index fb2169c1..afa6a249 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ See [Running on Railway](https://umami.is/docs/running-on-railway) to get starte ### Requirements -- A server with Node.js 12 or newer -- A database (MySQL or Postgresql) +- A server with Node.js version 12 or newer +- A database. Umami supports [MySQL](https://www.mysql.com/) and [Postgresql](https://www.postgresql.org/) databases. -### Install Yarn (if needed) +### Install Yarn ``` npm install -g yarn @@ -33,32 +33,12 @@ cd umami yarn install ``` -### Create database tables - -Umami supports [MySQL](https://www.mysql.com/) and [Postgresql](https://www.postgresql.org/). -Create a database for your Umami installation and install the tables with the included scripts. - -For MySQL: - -``` -mysql -u username -p databasename < sql/schema.mysql.sql -``` - -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 ``` DATABASE_URL=(connection url) -HASH_SALT=(any random string) ``` The connection url is in the following format: @@ -68,7 +48,7 @@ postgresql://username:mypassword@localhost:5432/mydb mysql://username:mypassword@localhost:3306/mydb ``` -The `HASH_SALT` is used to generate unique values for your installation. +This will also create a login account with username **admin** and password **umami**. ### Build the application @@ -76,13 +56,19 @@ The `HASH_SALT` is used to generate unique values for your installation. yarn build ``` +### Create database tables + +```bash +yarn update-db +``` + ### Start the application ```bash yarn start ``` -By default this will launch the application on `http://localhost:3000`. You will need to either +By default this will launch the application on `http://localhost:3000`. You will need to either [proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) requests from your web server or change the [port](https://nextjs.org/docs/api-reference/cli#production) to serve the application directly. @@ -112,6 +98,7 @@ To get the latest features, simply do a pull, install any new dependencies, and git pull yarn install yarn build +yarn update-db ``` To update the Docker image, simply pull the new images and rebuild: diff --git a/app.json b/app.json index a27dc6fe..ef62e0e5 100644 --- a/app.json +++ b/app.json @@ -1,26 +1,16 @@ { - "name": "Umami", - "description": "Umami is a simple, fast, website analytics alternative to Google Analytics.", - "keywords": [ - "analytics", - "charts", - "statistics", - "web-analytics" - ], - "website": "https://umami.is", - "repository": "https://github.com/mikecao/umami", - "addons": [ - "heroku-postgresql" - ], - "env": { - "HASH_SALT": { - "description": "Used to generate unique values for your installation", - "required": true, - "generator": "secret" - } - }, - "scripts": { - "postdeploy": "psql $DATABASE_URL -f sql/schema.postgresql.sql" - }, - "success_url": "/" + "name": "Umami", + "description": "Umami is a simple, fast, website analytics alternative to Google Analytics.", + "keywords": ["analytics", "charts", "statistics", "web-analytics"], + "website": "https://umami.is", + "repository": "https://github.com/mikecao/umami", + "addons": ["heroku-postgresql"], + "env": { + "HASH_SALT": { + "description": "Used to generate unique values for your installation", + "required": true, + "generator": "secret" + } + }, + "success_url": "/" } diff --git a/components/common/FilterLink.js b/components/common/FilterLink.js index 9401cb13..459a8ae1 100644 --- a/components/common/FilterLink.js +++ b/components/common/FilterLink.js @@ -2,6 +2,7 @@ import React from 'react'; import Link from 'next/link'; import classNames from 'classnames'; 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 styles from './FilterLink.module.css'; @@ -20,7 +21,7 @@ export default function FilterLink({ id, value, label, externalUrl }) { [styles.active]: active && selected, })} > - {label || value} + {safeDecodeURI(label || value)} {externalUrl && ( diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js index a31c2abf..a2963995 100644 --- a/components/common/UpdateNotice.js +++ b/components/common/UpdateNotice.js @@ -1,27 +1,38 @@ -import React from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import useVersion from 'hooks/useVersion'; -import styles from './UpdateNotice.module.css'; -import ButtonLayout from '../layout/ButtonLayout'; +import ButtonLayout from 'components/layout/ButtonLayout'; +import useStore, { checkVersion } from 'store/version'; +import { setItem } from 'lib/web'; +import { VERSION_CHECK, VERSION_URL } from 'lib/constants'; import Button from './Button'; -import useForceUpdate from '../../hooks/useForceUpdate'; +import styles from './UpdateNotice.module.css'; export default function UpdateNotice() { - const forceUpdate = useForceUpdate(); - const { hasUpdate, checked, latest, updateCheck } = useVersion(true); + const { latest, checked, hasUpdate } = useStore(); + const [dismissed, setDismissed] = useState(false); + + const updateCheck = useCallback(() => { + setItem(VERSION_CHECK, { version: latest, time: Date.now() }); + }, [latest]); function handleViewClick() { - location.href = 'https://github.com/mikecao/umami/releases'; updateCheck(); - forceUpdate(); + setDismissed(true); + location.href = VERSION_URL; } function handleDismissClick() { updateCheck(); - forceUpdate(); + setDismissed(true); } - if (!hasUpdate || checked) { + useEffect(() => { + if (!checked) { + checkVersion(); + } + }, []); + + if (!hasUpdate || dismissed) { return null; } diff --git a/components/layout/Footer.js b/components/layout/Footer.js index 603b9369..21ce0573 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -3,11 +3,11 @@ import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; import Link from 'components/common/Link'; import styles from './Footer.module.css'; -import useVersion from 'hooks/useVersion'; +import useStore from 'store/version'; import { HOMEPAGE_URL, VERSION_URL } from 'lib/constants'; export default function Footer() { - const { current } = useVersion(); + const { current } = useStore(); return (