From 2f33101ec314986285fc2fe25dd0b416956c2d1a Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Mon, 17 Aug 2026 18:23:21 +0100 Subject: [PATCH] build(frontend): update docker image to use production env --- docker-compose.prod.yml | 2 +- docker-compose.yml | 2 +- frontend/Dockerfile | 20 ++++++++++++----- frontend/env.sh | 10 +++++++++ frontend/frontend_nginx.conf | 15 +++++++++++++ frontend/index.html | 1 + frontend/src/App.tsx | 5 ++++- frontend/src/components/video/VideoCard.tsx | 3 ++- frontend/src/config.ts | 5 +++++ frontend/src/pages/ClipEdit.tsx | 3 ++- frontend/src/pages/NotFound.tsx | 24 +++++++++++++++++++++ frontend/src/utils/api/client.ts | 4 +++- frontend/src/vite-env.d.ts | 10 ++++++++- 13 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 frontend/env.sh create mode 100644 frontend/frontend_nginx.conf create mode 100644 frontend/src/config.ts create mode 100644 frontend/src/pages/NotFound.tsx diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fe9c167..63be4d2 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -38,7 +38,7 @@ services: depends_on: - backend ports: - - "5173:5173" + - "5173:80" restart: unless-stopped rtmp: diff --git a/docker-compose.yml b/docker-compose.yml index cc38d19..a4d90da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,7 @@ services: depends_on: - backend ports: - - "5173:5173" + - "5173:80" restart: unless-stopped rtmp: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index fdf971e..d5a0c37 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,19 @@ -FROM node:20-alpine - +# --- Build stage --- +FROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN --mount=type=cache,target=/root/.npm npm ci - COPY . . -EXPOSE 5173 -CMD ["npm", "run", "dev"] \ No newline at end of file +RUN npm run build + +# --- Production stage --- +FROM nginx:1.27-alpine AS prod +COPY --from=build /app/dist /usr/share/nginx/html +COPY frontend_nginx.conf /etc/nginx/conf.d/default.conf + +# Inject envs at runtime +COPY env.sh /docker-entrypoint.d/40-env.sh +RUN chmod +x /docker-entrypoint.d/40-env.sh + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/env.sh b/frontend/env.sh new file mode 100644 index 0000000..2142bf5 --- /dev/null +++ b/frontend/env.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu + +cat < /usr/share/nginx/html/env-config.js +window._env_ = { + VITE_API_URL: "${VITE_API_URL:-}", + VITE_FRONTEND_URL: "${VITE_FRONTEND_URL:-}", + VITE_GOOGLE_CLIENT_ID: "${VITE_GOOGLE_CLIENT_ID:-}" +}; +EOF \ No newline at end of file diff --git a/frontend/frontend_nginx.conf b/frontend/frontend_nginx.conf new file mode 100644 index 0000000..8aef7a3 --- /dev/null +++ b/frontend/frontend_nginx.conf @@ -0,0 +1,15 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 1dd4518..3a9b00f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9,6 +9,7 @@ + The VoD System diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 17b39f5..e427aea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,6 +15,8 @@ import EditVod from "./pages/EditVod.tsx"; import Profile from "./pages/Profile.tsx"; import StreamControl from "./pages/StreamControl.tsx"; import { GoogleOAuthProvider } from '@react-oauth/google'; +import {config} from "./config.ts"; + import NotFound from "./pages/NotFound.tsx"; function App() { @@ -23,7 +25,7 @@ function App() { }, []); return ( - + }> @@ -38,6 +40,7 @@ function App() { } /> } /> } /> + } /> diff --git a/frontend/src/components/video/VideoCard.tsx b/frontend/src/components/video/VideoCard.tsx index 8ffad2e..8de85e7 100644 --- a/frontend/src/components/video/VideoCard.tsx +++ b/frontend/src/components/video/VideoCard.tsx @@ -4,6 +4,7 @@ import { Link } from "react-router-dom"; import { useEffect, useRef, useState } from "react"; import { isThumbnailAvailable } from "../../utils/api/client.ts"; import { MoreVertical, Pencil, Trash2 } from "lucide-react"; +import {config} from "../../config.ts"; type VideoCardProps = { id: number, @@ -19,7 +20,7 @@ type VideoCardProps = { } const fallbackThumbnail = "../../../public/default_thumbnail.png"; -const API_URL = import.meta.env.VITE_API_URL; +const API_URL = config.apiUrl; const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className, mediaApiPath = '/api/v1/clips', playerPath = '/video', itemLabel = 'clip' }: VideoCardProps) => { const [timeAgo, setTimeAgo] = useState(dateToTimeAgo(stringToDate(createdAt))); diff --git a/frontend/src/config.ts b/frontend/src/config.ts new file mode 100644 index 0000000..eeb871c --- /dev/null +++ b/frontend/src/config.ts @@ -0,0 +1,5 @@ +export const config = { + apiUrl: window._env_?.VITE_API_URL ?? import.meta.env.VITE_API_URL ?? '', + frontendUrl: window._env_?.VITE_FRONTEND_URL ?? import.meta.env.VITE_FRONTEND_URL ?? '', + googleClientId: window._env_?.VITE_GOOGLE_CLIENT_ID ?? import.meta.env.VITE_GOOGLE_CLIENT_ID ?? '', +}; \ No newline at end of file diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index 38a216e..267f836 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -10,8 +10,9 @@ import { getJob } from "../utils/api/jobs" import type { VideoMetadata } from "../utils/types.ts"; import Box from "../components/Box.tsx"; import MetadataBox from "../components/video/MetadataBox.tsx"; +import {config} from "../config.ts"; -const API_URL = import.meta.env.VITE_API_URL; +const API_URL = config.apiUrl; const ClipEdit = () => { const location = useLocation(); diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx new file mode 100644 index 0000000..ba0ffac --- /dev/null +++ b/frontend/src/pages/NotFound.tsx @@ -0,0 +1,24 @@ +import { Lock } from 'lucide-react'; +import {useNavigate} from "react-router-dom"; + +const NotFound = () => { + const navigate = useNavigate(); + + const goHomePage = () => { + navigate("/") + } + + return ( +
+ +

+ This page does not exist. +

+

+ Click here to get back to the home page. +

+
+ ); +} + +export default NotFound; \ No newline at end of file diff --git a/frontend/src/utils/api/client.ts b/frontend/src/utils/api/client.ts index c4361d8..8d6df5f 100644 --- a/frontend/src/utils/api/client.ts +++ b/frontend/src/utils/api/client.ts @@ -1,4 +1,6 @@ -const API_URL = import.meta.env.VITE_API_URL; +import {config} from "../../config.ts"; + +const API_URL = config.apiUrl; class AuthError extends Error { constructor() { super("Not authenticated"); this.name = "AuthError"; } diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 151aa68..f6d268d 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1 +1,9 @@ -/// \ No newline at end of file +/// + +interface Window { + _env_?: { + VITE_API_URL?: string; + VITE_FRONTEND_URL?: string; + VITE_GOOGLE_CLIENT_ID?: string; + }; +} \ No newline at end of file