diff --git a/.gitignore b/.gitignore index 7b512e91f..e051fd9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ uv.lock .mypy_cache .dmypy.json .pytest_cache -.yarn *.env !ci.env @@ -15,7 +14,6 @@ node_modules coverage.xml coverage.json -backend/yarn.lock backend/static /process-compose.yml diff --git a/backend/yarn.lock b/backend/yarn.lock deleted file mode 100644 index fb57ccd13..000000000 --- a/backend/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/docs/.gitignore b/docs/.gitignore index ed9b6db3e..5fdaaabb5 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -18,5 +18,3 @@ next-env.d.ts _pagefind/ npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/docs/content/community/development.mdx b/docs/content/community/development.mdx index ee6e633a4..2f9bb0065 100644 --- a/docs/content/community/development.mdx +++ b/docs/content/community/development.mdx @@ -35,7 +35,7 @@ The database URL can be specified per environment in the `.env` files (see ## Running the frontend and backend -To run Bracket (frontend and backend) locally without Docker, one needs `yarn` and `uv`. +To run Bracket (frontend and backend) locally without Docker, one needs `pnpm` and `uv`. The following starts the frontend and backend for local development in the root directory of Bracket: diff --git a/docs/content/deployment/cloud-services.mdx b/docs/content/deployment/cloud-services.mdx index 7a57182ad..ea61e0000 100644 --- a/docs/content/deployment/cloud-services.mdx +++ b/docs/content/deployment/cloud-services.mdx @@ -1,8 +1,34 @@ --- title: Cloud services --- +import {Callout} from "nextra/components" + # Cloud services +The frontend of Bracket is powered by Vite and can be simply statically hosted on any cloud +service, such as Vercel or Cloudflare. Vite's [documentation](https://vite.dev/guide/static-deploy) +provides detailed instructions on how to deploy your Vite application. + + +Essentially, the only thing you (or a cloud provider) needs to do is to run `pnpm run build` +to generate a production build, and then serve the contents of the `dist` directory to the internet. + + +## Cloudflare + +To deploy the frontend to Cloudflare, go to `Workers & Pages` and click on +`Import an existing Git repository`. + +Make sure to: + +- Select `React (Vite)` as framework +- Select the `frontend` directory as root directory + +## GitHub Pages + +To deploy the frontend to GitHub Pages, follow the steps outlined in +Vite's [docs](https://vite.dev/guide/static-deploy#github-pages). + ## Vercel To deploy the frontend to Vercel, use the following link: @@ -11,4 +37,18 @@ To deploy the frontend to Vercel, use the following link: https://vercel.com/new/project?template=https://github.com/evroon/bracket ``` -Make sure to select the `frontend` directory as root directory, and use Next.js as framework. +Make sure to: + +- Select `Vite` as framework +- Select the `frontend` directory as root directory + +This should work. If it fails, Vercel didn't automatically detect the right build settings. +Change the following settings under `Build and Output settings`: + +- Set the build command to `pnpm run build` +- Set the install command to `pnpm install` + +## Other cloud providers + +Vite's [documentation](https://vite.dev/guide/static-deploy) +provides detailed instructions on how to deploy your Vite application. diff --git a/docs/content/deployment/systemd.mdx b/docs/content/deployment/systemd.mdx index 03ebb863a..b86151465 100644 --- a/docs/content/deployment/systemd.mdx +++ b/docs/content/deployment/systemd.mdx @@ -8,7 +8,7 @@ This section describes how to deploy Bracket (frontend and backend) as a Systemd This assumes: -- You have installed `yarn` and `pipenv`. +- You have installed `pnpm` and `uv`. - You have a PostgreSQL cluster running. - You have cloned Bracket in `/var/lib/bracket`. - You have created a new user called Bracket with the permissions to read @@ -29,7 +29,7 @@ After=network.target Type=simple User=bracket WorkingDirectory=/var/lib/bracket/backend -ExecStart=pipenv run gunicorn -k uvicorn.workers.UvicornWorker bracket.app:app --bind localhost:8400 --workers 1 +ExecStart=uv run gunicorn -k uvicorn.workers.UvicornWorker bracket.app:app --bind localhost:8400 --workers 1 Environment=ENVIRONMENT=PRODUCTION TimeoutSec=15 Restart=always diff --git a/docs/content/running-bracket/configuration.mdx b/docs/content/running-bracket/configuration.mdx index ac07f6719..9d8ec4050 100644 --- a/docs/content/running-bracket/configuration.mdx +++ b/docs/content/running-bracket/configuration.mdx @@ -22,7 +22,7 @@ Copy `ci.env` to `prod.env` and fill in the values: - `ALLOW_INSECURE_HTTP_SSO`: Should not be used in production. Allows use of INSECURE requests for SSO auth. - `AUTO_RUN_MIGRATIONS`: Whether to run (alembic) migrations automatically on startup or not. - Migrations can be applied manually using `pipenv run alembic upgrade head`. + Migrations can be applied manually using `uv run alembic upgrade head`. ### Backend: Example configuration file diff --git a/docs/content/running-bracket/quickstart.mdx b/docs/content/running-bracket/quickstart.mdx index bbb2b80f2..4c1410c0d 100644 --- a/docs/content/running-bracket/quickstart.mdx +++ b/docs/content/running-bracket/quickstart.mdx @@ -22,5 +22,5 @@ be able to view bracket at `http://localhost:3000`. You can log in with the foll To insert dummy rows into the database, run: ```bash -sudo docker exec bracket-backend pipenv run ./cli.py create-dev-db +sudo docker exec bracket-backend uv run ./cli.py create-dev-db ``` diff --git a/docs/package.json b/docs/package.json index 94dbbbdb7..5d311ad1a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,7 +8,7 @@ "start": "next start", "prettier:check": "prettier --check \"**/*.{js,jsx,ts,tsx}\"", "prettier:write": "prettier --write \"**/*.{js,jsx,ts,tsx}\"", - "test": "pnpm run prettier:write && pnpm markdownlint-cli2 --fix", + "test": "pnpm run prettier:write && pnpm markdownlint-cli2 --fix --config .markdownlint-cli2.mjs", "test-check": "pnpm run prettier:check && pnpm markdownlint-cli2 --config .markdownlint-cli2.mjs", "lint:markdown": "markdownlint-cli2", "postbuild": "pagefind --site .next/server/app --output-path out/_pagefind && next-sitemap" diff --git a/frontend/.gitignore b/frontend/.gitignore index d838d0a51..5bde71803 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -22,8 +22,6 @@ # debug npm-debug.log* -yarn-debug.log* -yarn-error.log* # local env files .env.local diff --git a/frontend/Caddyfile b/frontend/Caddyfile new file mode 100644 index 000000000..46c8ad9a9 --- /dev/null +++ b/frontend/Caddyfile @@ -0,0 +1,5 @@ +:3000 { + root * /app/dist + file_server + try_files {path} /index.html +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a57fa5347..02bcfc908 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,47 +1,23 @@ -# Install dependencies only when needed -FROM node:22-alpine AS deps - -WORKDIR /app - -COPY pnpm-lock.yaml package.json ./ - -RUN corepack enable && pnpm i - -# Rebuild the source code only when needed +# Build static files FROM node:22-alpine AS builder WORKDIR /app -COPY . . -COPY --from=deps /app/node_modules ./node_modules +ENV NODE_ENV=production -RUN corepack enable +COPY . . -RUN VITE_API_BASE_URL=http://VITE_API_BASE_URL_PLACEHOLDER \ - VITE_HCAPTCHA_SITE_KEY=VITE_HCAPTCHA_SITE_KEY_PLACEHOLDER \ - pnpm build +RUN corepack enable && pnpm install && pnpm build -# Production image, copy all the files and run next -FROM node:22-alpine AS runner +# Production image, copy all the static files and run next +FROM caddy:2-alpine AS runner WORKDIR /app -ENV NODE_ENV=production - -RUN addgroup -g 1001 --system nodejs && \ - adduser --system vite -u 1001 -G nodejs - -COPY --from=builder --chown=vite:nodejs /app/public ./public -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/package.json ./package.json - -RUN apk add bash - -USER vite +COPY --from=builder /app/dist /app/dist +COPY --from=builder /app/Caddyfile /etc/caddy/Caddyfile EXPOSE 3000 HEALTHCHECK --interval=10s --timeout=5s --retries=5 \ CMD ["wget", "--spider", "http://0.0.0.0:3000", "||", "exit", "1"] - -CMD ["pnpm", "start"] diff --git a/frontend/LICENCE b/frontend/LICENCE deleted file mode 100644 index 1a88111ae..000000000 --- a/frontend/LICENCE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Vitaly Rtischev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/frontend/src/components/utils/error_alert.tsx b/frontend/src/components/utils/error_alert.tsx index 528dbaebf..89bcfb3d5 100644 --- a/frontend/src/components/utils/error_alert.tsx +++ b/frontend/src/components/utils/error_alert.tsx @@ -1,4 +1,4 @@ -import { Alert } from '@mantine/core'; +import { Alert, Center } from '@mantine/core'; import { IconAlertCircle } from '@tabler/icons-react'; import React from 'react'; @@ -10,6 +10,7 @@ export function ErrorAlert({ title, message }: { title: string; message: string color="red" radius="lg" variant="outline" + w="40rem" > {message} @@ -23,5 +24,9 @@ export default function RequestErrorAlert({ error }: any) { : 'Error'; const message = `${status_code}: ${error.response ? error.response.data.detail : error.message}`; - return ; + return ( +
+ +
+ ); } diff --git a/frontend/src/pages/404.tsx b/frontend/src/pages/404.tsx index c15ecaa16..70365d70e 100644 --- a/frontend/src/pages/404.tsx +++ b/frontend/src/pages/404.tsx @@ -2,6 +2,7 @@ import { Button, Container, Group, Text, Title } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; +import { tokenPresent } from '../services/local_storage'; import classes from './404.module.css'; export default function NotFoundPage() { @@ -16,7 +17,11 @@ export default function NotFoundPage() { {t('not_found_description')} - diff --git a/frontend/src/pages/user.tsx b/frontend/src/pages/user.tsx index 03cece25c..8672b8077 100644 --- a/frontend/src/pages/user.tsx +++ b/frontend/src/pages/user.tsx @@ -1,7 +1,9 @@ import { Group, Stack, Title } from '@mantine/core'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import UserForm from '../components/forms/user'; +import RequestErrorAlert from '../components/utils/error_alert'; import { TableSkeletonSingleColumn } from '../components/utils/skeletons'; import { checkForAuthError, getUser } from '../services/adapter'; import Layout from './_layout'; @@ -26,6 +28,7 @@ export default function UserPage() { return ( {t('edit_profile_title')} + {swrUserResponse.error && } {content} ); diff --git a/frontend/src/services/local_storage.tsx b/frontend/src/services/local_storage.tsx index 6f44a0207..012457c84 100644 --- a/frontend/src/services/local_storage.tsx +++ b/frontend/src/services/local_storage.tsx @@ -19,7 +19,7 @@ export function performLogoutAndRedirect(t: Translator, navigate: NavigateFuncti message: '', autoClose: 10000, }); - navigate('/login'); + navigate('/login', { replace: true }); } export function getLogin() { diff --git a/process-compose-example.yml b/process-compose-example.yml index 68eead471..1b927355b 100644 --- a/process-compose-example.yml +++ b/process-compose-example.yml @@ -4,7 +4,7 @@ log_level: debug processes: frontend: working_dir: "frontend" - command: "yarn run dev" + command: "pnpm run dev" availability: restart: "on_failure" readiness_probe: @@ -19,7 +19,7 @@ processes: backend: working_dir: "backend" - command: "pipenv run gunicorn -k bracket.uvicorn.RestartableUvicornWorker bracket.app:app --bind localhost:8400 --workers 1 --reload" + command: "uv run gunicorn -k bracket.uvicorn.RestartableUvicornWorker bracket.app:app --bind localhost:8400 --workers 1 --reload" availability: restart: "on_failure" environment: @@ -36,7 +36,7 @@ processes: docs: working_dir: "docs" - command: "yarn run dev -p 3001" + command: "pnpm run dev -p 3001" availability: restart: "on_failure" readiness_probe: