From fde60b7d68578762cdd3b27b4cb61c78f8c985c2 Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Thu, 27 Mar 2025 15:28:40 -0700 Subject: [PATCH] feat: Implement frontend asset build process and update Docker configuration - Added a new build script using esbuild to bundle JavaScript and CSS assets, including Tailwind CSS and Font Awesome. - Updated the Dockerfile to include a multi-stage build process for frontend assets, ensuring efficient image creation. - Enhanced GitHub workflows to automate the installation of Node.js dependencies and build frontend assets during CI/CD. - Introduced a .gitignore entry for the dist directory to prevent unnecessary files from being tracked. - Improved caching and content type handling for static files served by the application. --- .../workflows/docker-publish-dockerhub.yml | 17 + .github/workflows/docker-publish.yml | 17 + .github/workflows/go-release.yml | 20 + .gitignore | 2 + Dockerfile | 47 +- build.js | 122 ++ components/admin_roles.templ | 19 +- components/configs.templ | 33 +- components/file_metadata.templ | 31 +- components/jobs.templ | 27 +- components/layout.templ | 212 +- components/notifications.templ | 27 +- components/providers/destination/ftp.templ | 8 +- components/providers/destination/gdrive.templ | 14 +- .../providers/destination/gphotos.templ | 10 +- components/providers/destination/minio.templ | 8 +- .../providers/destination/nextcloud.templ | 6 +- components/providers/destination/s3.templ | 8 +- components/providers/destination/sftp.templ | 8 +- components/providers/destination/smb.templ | 10 +- components/providers/destination/webdav.templ | 6 +- components/user_management.templ | 23 +- internal/db/db.go | 11 +- main.go | 51 +- package-lock.json | 1872 +++++++++++++++++ package.json | 19 + static/css/app.css | 51 + static/js/init.js | 32 + static/js/vendor.js | 53 + tailwind.config.js | 33 + 30 files changed, 2556 insertions(+), 241 deletions(-) create mode 100644 build.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 static/js/init.js create mode 100644 static/js/vendor.js create mode 100644 tailwind.config.js diff --git a/.github/workflows/docker-publish-dockerhub.yml b/.github/workflows/docker-publish-dockerhub.yml index 2990b9b..01f95ac 100644 --- a/.github/workflows/docker-publish-dockerhub.yml +++ b/.github/workflows/docker-publish-dockerhub.yml @@ -29,6 +29,23 @@ jobs: with: fetch-depth: 0 # Needed to get all tags for versioning + # Set up Node.js for frontend build + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + # Install dependencies + - name: Install dependencies + run: npm ci + + # Build frontend assets + - name: Build frontend assets + run: | + node build.js + ls -la static/dist/ + # Set version information - name: Set Version id: version diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7fc262c..479fd70 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -30,6 +30,23 @@ jobs: with: fetch-depth: 0 # Needed to get all tags for versioning + # Set up Node.js for frontend build + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + # Install dependencies + - name: Install dependencies + run: npm ci + + # Build frontend assets + - name: Build frontend assets + run: | + node build.js + ls -la static/dist/ + # Set version information - name: Set Version id: version diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index 3a7fcb0..8b9cb28 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -27,6 +27,26 @@ jobs: with: fetch-depth: 0 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install Node.js dependencies + run: npm ci + + - name: Build frontend assets + run: | + # Build JavaScript and CSS assets + node build.js + + # Ensure the dist directory exists + mkdir -p static/dist + + # Verify the build output + ls -la static/dist + - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/.gitignore b/.gitignore index b8726fd..935422f 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,8 @@ backups/ /destination/ /archive/ +# Ignore the dist directory +static/dist/ # Ignore binaries gomft \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 57f166a..0785d97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,32 @@ +# Build frontend assets +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +# Copy all files needed for the build first +COPY package.json package-lock.json ./ +COPY build.js ./ +COPY static/ ./static/ +COPY tailwind.config.js ./ + +# Debug: Show the contents of build.js +RUN echo "Contents of build.js:" && cat build.js + +# Remove the build script from postinstall +RUN sed -i 's/"postinstall": "npm run build",//' package.json + +# Install dependencies and build with verbose output +RUN npm ci && \ + echo "Building frontend assets..." && \ + node build.js && \ + echo "Build complete. Contents of dist:" && \ + ls -la static/dist/ && \ + echo "Sample of app.js:" && \ + head -n 10 static/dist/app.js && \ + echo "Sample of app.css:" && \ + head -n 10 static/dist/app.css + +# Go build stage FROM golang:1.24-alpine AS builder WORKDIR /app @@ -17,7 +46,20 @@ RUN go install github.com/a-h/templ/cmd/templ@latest COPY go.mod go.sum ./ RUN go mod download -# Copy the rest of the source code +# Create static directory structure +RUN mkdir -p /app/static/dist + +# Copy built frontend assets from frontend-builder BEFORE copying Go source +COPY --from=frontend-builder /app/static/dist/ /app/static/dist/ + +# Copy the rest of the static files +COPY static/ /app/static/ + +# Verify static files are in place before Go build +RUN echo "Verifying static files before Go build:" && \ + ls -la /app/static/dist/ + +# Now copy the rest of the source code COPY . . # Generate template files from .templ files @@ -61,8 +103,7 @@ RUN addgroup -g ${GID} ${USERNAME} && \ COPY --from=builder /app/gomft /app/ COPY --from=builder /usr/local/bin/rclone /usr/local/bin/rclone -# Copy static files and configurations -COPY static/ /app/static/ +# Copy components COPY components/ /app/components/ # Copy entrypoint script diff --git a/build.js b/build.js new file mode 100644 index 0000000..4d39828 --- /dev/null +++ b/build.js @@ -0,0 +1,122 @@ +import * as esbuild from 'esbuild'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const isWatch = process.argv.includes('--watch'); + +// Ensure the dist directory exists +const distDir = path.join(__dirname, 'static', 'dist'); +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +// Copy Font Awesome files +const fontAwesomeSrcDir = path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free'); +const fontAwesomeDestDir = path.join(distDir, 'fontawesome'); + +// Copy CSS files +const cssFiles = [ + 'css/all.min.css', + 'css/fontawesome.min.css', + 'css/solid.min.css', + 'css/regular.min.css', + 'css/brands.min.css' +]; + +cssFiles.forEach(file => { + const srcFile = path.join(fontAwesomeSrcDir, file); + const destFile = path.join(fontAwesomeDestDir, file); + const destDir = path.dirname(destFile); + + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + if (fs.existsSync(srcFile)) { + fs.copyFileSync(srcFile, destFile); + } +}); + +// Copy webfonts +const webfontsSrcDir = path.join(fontAwesomeSrcDir, 'webfonts'); +const webfontsDestDir = path.join(fontAwesomeDestDir, 'webfonts'); + +if (!fs.existsSync(webfontsDestDir)) { + fs.mkdirSync(webfontsDestDir, { recursive: true }); +} + +fs.readdirSync(webfontsSrcDir).forEach(file => { + fs.copyFileSync( + path.join(webfontsSrcDir, file), + path.join(webfontsDestDir, file) + ); +}); + +const commonConfig = { + sourcemap: true, + minify: true, + bundle: true, + platform: 'browser', + target: ['es2020'], +}; + +async function buildTailwind() { + console.log('Building Tailwind CSS...'); + execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --minify'); +} + +async function build() { + try { + // Build vendor JavaScript bundle (CDN dependencies) + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/vendor.js'], + outfile: 'static/dist/vendor.js', + format: 'iife', + }); + + // Build application JavaScript + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/app.js'], + outfile: 'static/dist/app.js', + format: 'iife', + }); + + // Build initialization JavaScript + await esbuild.build({ + ...commonConfig, + entryPoints: ['static/js/init.js'], + outfile: 'static/dist/init.js', + format: 'iife', + }); + + // Build CSS with Tailwind + await buildTailwind(); + + console.log('Build completed successfully!'); + } catch (error) { + console.error('Build failed:', error); + process.exit(1); + } +} + +if (isWatch) { + // Watch mode + console.log('Starting watch mode...'); + const ctx = await esbuild.context(commonConfig); + await ctx.watch(); + + // Watch Tailwind CSS + execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --watch'); + + console.log('Watching for changes...'); +} else { + // Single build + build(); +} \ No newline at end of file diff --git a/components/admin_roles.templ b/components/admin_roles.templ index a39213c..3ba3de0 100644 --- a/components/admin_roles.templ +++ b/components/admin_roles.templ @@ -87,7 +87,7 @@ templ AdminRoles(ctx context.Context, data RolesData) { @@ -269,7 +269,10 @@ templ AdminRoles(ctx context.Context, data RolesData) { // RoleDialog for confirmation actions templ RoleDialog(id string, title string, message string, confirmClass string, confirmText string, action string, roleID uint, roleName string) { - diff --git a/components/providers/destination/gphotos.templ b/components/providers/destination/gphotos.templ index 720fed5..870d310 100644 --- a/components/providers/destination/gphotos.templ +++ b/components/providers/destination/gphotos.templ @@ -1,10 +1,10 @@ package destination templ GooglePhotosDestinationForm() { -
@@ -30,7 +30,7 @@ templ MinIODestinationForm() {
-
@@ -45,7 +45,7 @@ templ MinIODestinationForm() {
- @@ -60,7 +60,7 @@ templ MinIODestinationForm() {
- diff --git a/components/providers/destination/nextcloud.templ b/components/providers/destination/nextcloud.templ index d093a37..bb4c42b 100644 --- a/components/providers/destination/nextcloud.templ +++ b/components/providers/destination/nextcloud.templ @@ -15,7 +15,7 @@ templ NextCloudDestinationForm() {
- @@ -30,7 +30,7 @@ templ NextCloudDestinationForm() {
- @@ -45,7 +45,7 @@ templ NextCloudDestinationForm() {
- diff --git a/components/providers/destination/s3.templ b/components/providers/destination/s3.templ index 1e08420..b33ccce 100644 --- a/components/providers/destination/s3.templ +++ b/components/providers/destination/s3.templ @@ -15,7 +15,7 @@ templ S3DestinationForm() {
- @@ -30,7 +30,7 @@ templ S3DestinationForm() {
- @@ -45,7 +45,7 @@ templ S3DestinationForm() {
- @@ -60,7 +60,7 @@ templ S3DestinationForm() {
- diff --git a/components/providers/destination/sftp.templ b/components/providers/destination/sftp.templ index 5b865c0..21ec906 100644 --- a/components/providers/destination/sftp.templ +++ b/components/providers/destination/sftp.templ @@ -8,7 +8,7 @@ templ SFTPDestinationForm() {
- @@ -21,7 +21,7 @@ templ SFTPDestinationForm() {
- @@ -34,7 +34,7 @@ templ SFTPDestinationForm() {
- @@ -61,7 +61,7 @@ templ SFTPDestinationForm() {
- diff --git a/components/providers/destination/smb.templ b/components/providers/destination/smb.templ index 06eff84..4c80370 100644 --- a/components/providers/destination/smb.templ +++ b/components/providers/destination/smb.templ @@ -15,7 +15,7 @@ templ SMBDestinationForm() {
- @@ -30,7 +30,7 @@ templ SMBDestinationForm() {
- @@ -45,7 +45,7 @@ templ SMBDestinationForm() {
- @@ -60,7 +60,7 @@ templ SMBDestinationForm() {
- @@ -75,7 +75,7 @@ templ SMBDestinationForm() {
- diff --git a/components/providers/destination/webdav.templ b/components/providers/destination/webdav.templ index 9fd1e2e..15caa59 100644 --- a/components/providers/destination/webdav.templ +++ b/components/providers/destination/webdav.templ @@ -15,7 +15,7 @@ templ WebDAVDestinationForm() {
- @@ -30,7 +30,7 @@ templ WebDAVDestinationForm() {
- @@ -45,7 +45,7 @@ templ WebDAVDestinationForm() {
- diff --git a/components/user_management.templ b/components/user_management.templ index 9d8fa7b..b812ea2 100644 --- a/components/user_management.templ +++ b/components/user_management.templ @@ -117,9 +117,7 @@ templ UserManagementContent(data UserManagementData) { }); // Track user operations in HTMX events - document.addEventListener('htmx:beforeRequest', function(event) { - console.log("HTMX before request:", event.detail); - + document.addEventListener('htmx:beforeRequest', function(event) { // Check if this is a DELETE request for a user const path = event.detail.path; const method = event.detail.verb; @@ -421,7 +419,7 @@ templ userList(users []db.User) { class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800" data-user-email={ user.Email } data-user-id={ strconv.Itoa(int(user.ID)) } - onclick={ showUserDialog(fmt.Sprintf("delete-user-dialog-%d", user.ID)) } + onclick={ showModal(fmt.Sprintf("delete-user-dialog-%d", user.ID)) } > Delete @@ -634,7 +632,10 @@ func hasRole(user *db.User, roleID uint) bool { // UserDialog for confirmation actions templ UserDialog(id string, title string, message string, confirmClass string, confirmText string, action string, userID uint, userEmail string) { -