From 75a2ccc2269e5dfb568c7218de0b75ac2bcddced Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:34:02 +0200 Subject: [PATCH] Restructure Web App into server/ + web/ (GsmNode parity) Reorganize the Web App to match the GsmNode project layout: a Go backend-for-frontend in server/ that embeds the built SPA and reverse-proxies /api/* to the API Server, with the Vue 3 + Vite frontend moved into web/. - Move all frontend files into web/ (history preserved via renames) - Point vite build output at ../server/dist for Go embedding - Add server/ Go BFF (main.go, go.mod, .env.example, Run-WebApp.ps1) - Drop Docker/nginx deploy (Dockerfile, docker-compose.yml, nginx.conf.template, .dockerignore) in favor of the BFF, matching GsmNode - Update .claude/launch.json to run the dev server from web/ - Rewrite README.md for the new layout Co-Authored-By: Claude Opus 4.8 --- Web App/.claude/launch.json | 1 + Web App/.dockerignore | 15 --- Web App/.gitignore | 24 ---- Web App/Dockerfile | 33 ----- Web App/README.md | 94 +++++++++------ Web App/docker-compose.yml | 18 --- Web App/nginx.conf.template | 35 ------ Web App/server/.env.example | 8 ++ Web App/server/.gitignore | 5 + Web App/server/Run-WebApp.ps1 | 29 +++++ Web App/server/go.mod | 3 + Web App/server/main.go | 113 ++++++++++++++++++ Web App/web/.gitignore | 4 + Web App/{ => web}/index.html | 0 Web App/{ => web}/package-lock.json | 0 Web App/{ => web}/package.json | 0 Web App/{ => web}/public/apple-touch-icon.png | Bin .../public/brand/drivervault-appicon-256.png | Bin .../public/brand/drivervault-appicon.svg | 0 .../public/brand/drivervault-icon-mono.svg | 0 .../public/brand/drivervault-icon-white.svg | 0 .../public/brand/drivervault-icon.svg | 0 .../public/brand/drivervault-lockup-dark.svg | 0 .../public/brand/drivervault-lockup.svg | 0 Web App/{ => web}/public/favicon-16.png | Bin Web App/{ => web}/public/favicon-32.png | Bin Web App/{ => web}/public/favicon.svg | 0 Web App/{ => web}/src/App.vue | 0 Web App/{ => web}/src/api.js | 0 Web App/{ => web}/src/auth.js | 0 .../{ => web}/src/components/CarFormModal.vue | 0 Web App/{ => web}/src/components/Logo.vue | 0 Web App/{ => web}/src/components/Modal.vue | 0 .../src/components/PartFormModal.vue | 0 .../src/components/ServiceFormModal.vue | 0 .../{ => web}/src/components/ShareModal.vue | 0 Web App/{ => web}/src/lib/format.js | 0 Web App/{ => web}/src/main.js | 0 Web App/{ => web}/src/prefs.js | 0 Web App/{ => web}/src/router.js | 0 Web App/{ => web}/src/style.css | 0 Web App/{ => web}/src/views/AdminUsers.vue | 0 Web App/{ => web}/src/views/CarDetail.vue | 0 Web App/{ => web}/src/views/Dashboard.vue | 0 Web App/{ => web}/src/views/Login.vue | 0 Web App/{ => web}/src/views/Settings.vue | 0 Web App/{ => web}/vite.config.js | 9 +- 47 files changed, 230 insertions(+), 161 deletions(-) delete mode 100644 Web App/.dockerignore delete mode 100644 Web App/.gitignore delete mode 100644 Web App/Dockerfile delete mode 100644 Web App/docker-compose.yml delete mode 100644 Web App/nginx.conf.template create mode 100644 Web App/server/.env.example create mode 100644 Web App/server/.gitignore create mode 100644 Web App/server/Run-WebApp.ps1 create mode 100644 Web App/server/go.mod create mode 100644 Web App/server/main.go create mode 100644 Web App/web/.gitignore rename Web App/{ => web}/index.html (100%) rename Web App/{ => web}/package-lock.json (100%) rename Web App/{ => web}/package.json (100%) rename Web App/{ => web}/public/apple-touch-icon.png (100%) rename Web App/{ => web}/public/brand/drivervault-appicon-256.png (100%) rename Web App/{ => web}/public/brand/drivervault-appicon.svg (100%) rename Web App/{ => web}/public/brand/drivervault-icon-mono.svg (100%) rename Web App/{ => web}/public/brand/drivervault-icon-white.svg (100%) rename Web App/{ => web}/public/brand/drivervault-icon.svg (100%) rename Web App/{ => web}/public/brand/drivervault-lockup-dark.svg (100%) rename Web App/{ => web}/public/brand/drivervault-lockup.svg (100%) rename Web App/{ => web}/public/favicon-16.png (100%) rename Web App/{ => web}/public/favicon-32.png (100%) rename Web App/{ => web}/public/favicon.svg (100%) rename Web App/{ => web}/src/App.vue (100%) rename Web App/{ => web}/src/api.js (100%) rename Web App/{ => web}/src/auth.js (100%) rename Web App/{ => web}/src/components/CarFormModal.vue (100%) rename Web App/{ => web}/src/components/Logo.vue (100%) rename Web App/{ => web}/src/components/Modal.vue (100%) rename Web App/{ => web}/src/components/PartFormModal.vue (100%) rename Web App/{ => web}/src/components/ServiceFormModal.vue (100%) rename Web App/{ => web}/src/components/ShareModal.vue (100%) rename Web App/{ => web}/src/lib/format.js (100%) rename Web App/{ => web}/src/main.js (100%) rename Web App/{ => web}/src/prefs.js (100%) rename Web App/{ => web}/src/router.js (100%) rename Web App/{ => web}/src/style.css (100%) rename Web App/{ => web}/src/views/AdminUsers.vue (100%) rename Web App/{ => web}/src/views/CarDetail.vue (100%) rename Web App/{ => web}/src/views/Dashboard.vue (100%) rename Web App/{ => web}/src/views/Login.vue (100%) rename Web App/{ => web}/src/views/Settings.vue (100%) rename Web App/{ => web}/vite.config.js (69%) diff --git a/Web App/.claude/launch.json b/Web App/.claude/launch.json index 193ff6a..3263e92 100644 --- a/Web App/.claude/launch.json +++ b/Web App/.claude/launch.json @@ -5,6 +5,7 @@ "name": "web", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], + "cwd": "web", "port": 5173 } ] diff --git a/Web App/.dockerignore b/Web App/.dockerignore deleted file mode 100644 index 42661fc..0000000 --- a/Web App/.dockerignore +++ /dev/null @@ -1,15 +0,0 @@ -# Keep the build context small; node_modules and dist are rebuilt in the image. -node_modules/ -dist/ -dist-ssr/ - -# Logs and local files -*.log -*.local - -# VCS / editor noise -.git/ -.gitignore -.vscode/ -.idea/ -.DS_Store diff --git a/Web App/.gitignore b/Web App/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/Web App/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/Web App/Dockerfile b/Web App/Dockerfile deleted file mode 100644 index ccf6505..0000000 --- a/Web App/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# syntax=docker/dockerfile:1 - -# --- Build stage ------------------------------------------------------------- -# Build the Vue 3 + Vite SPA into static assets. -FROM node:22-alpine AS build - -WORKDIR /app - -# Install dependencies against the lockfile for reproducible builds. -COPY package.json package-lock.json ./ -RUN npm ci - -COPY . . - -# VITE_API_BASE is baked into the bundle at build time. Leave empty to use the -# same-origin "/api" (nginx proxies it to the API Server — see nginx.conf). -ARG VITE_API_BASE -RUN npm run build - -# --- Runtime stage ----------------------------------------------------------- -# Serve the built assets with nginx (Alpine-based) and proxy /api upstream. -FROM nginx:alpine - -# nginx substitutes ${API_TARGET} into this template at container start -# (only env-defined vars are replaced, so $uri/$host are left intact). -COPY nginx.conf.template /etc/nginx/templates/default.conf.template -COPY --from=build /app/dist /usr/share/nginx/html - -# Upstream API Server; override at runtime (compose/`docker run -e`). -ENV API_TARGET=http://api-server:8080 - -EXPOSE 80 -# The default nginx entrypoint renders templates then launches nginx. diff --git a/Web App/README.md b/Web App/README.md index c75227a..cd3394d 100644 --- a/Web App/README.md +++ b/Web App/README.md @@ -1,27 +1,78 @@ # DriverVault — Web App -Vue 3 + Vite + Tailwind CSS v4 maintenance tracker. Talks **only** to the API -Server (never to PocketBase directly). Built from `Car Service.xlsx`. Plain JS -(not TS). Vue calls the central API Server directly — there is no separate web -backend. +Maintenance tracker for your cars: a **Vue 3 + Vite + Tailwind CSS v4** SPA served +by a small **Go backend-for-frontend (BFF)**. The BFF serves the built SPA and +reverse-proxies `/api/*` to the API Server, so the browser is always same-origin +and all data access still flows through the API Server (never PocketBase directly). -## Run +``` +Browser ─► Web App BFF (:8090) ──/api/*──► API Server (:8080) ─► PocketBase + └── serves embedded Vue SPA +``` + +## Layout + +``` +server/ Go BFF: embeds web/dist, proxies /api -> API_BASE + main.go + .env.example + dist/ built SPA (generated; embedded at compile time) +web/ Vue 3 + Vite + Tailwind v4 source + src/ + main.js app bootstrap + router.js /login, / (dashboard), /cars/:id, /settings, /admin + api.js the only place that calls the API Server (base URL resolution) + auth.js session/profile state, isAdmin + prefs.js theme/locale/date/font preferences -> + lib/format.js date/km formatting + next-service status badges + style.css Tailwind v4 entry (+ dark custom-variant) + App.vue layout shell + nav (Admin link when admin) + components/ Modal, CarFormModal, ServiceFormModal, PartFormModal, ShareModal, Logo + views/ Login, Dashboard, CarDetail, Settings, AdminUsers +``` + +## Requirements + +- Node 18+ and Go 1.26+ +- A running **API Server** (see `../API Server`) + +## Develop + +Two terminals: ```powershell -npm install -npm run dev # http://localhost:5173 +# terminal 1 — API Server (see ../API Server/README.md) +cd "../API Server"; ./api-server.exe + +# terminal 2 — Vite dev server with hot reload (proxies /api -> :8080) +cd web; npm install; npm run dev # http://localhost:5173 ``` The dev server proxies `/api/*` to the API Server (default `http://localhost:8080`, override with `VITE_API_TARGET`), so the client uses same-origin relative URLs and -avoids CORS. The **API Server must be running** — see `../API Server/README.md`. -The dev server also listens on all interfaces (`host: true`) so it's reachable on +avoids CORS. It also listens on all interfaces (`host: true`) so it's reachable on the LAN (e.g. `http://10.2.1.101:5173`). At runtime, users can override the API base URL from the login screen's **Server settings** (persisted in `localStorage` as `cc_server_url`); resolution order is that override → `VITE_API_BASE` → `/api`. +## Build & run (production-style) + +```powershell +./server/Run-WebApp.ps1 # builds frontend, then serves on :8090 +# or manually: +cd web; npm run build # outputs to ../server/dist +cd ../server; go run . # http://localhost:8090 +``` + +Config (`server/.env`, copy from `.env.example`): + +| Variable | Purpose | Default | +|---|---|---| +| `WEB_ADDR` | Listen address | `:8090` | +| `API_BASE` | API Server base URL | `http://localhost:8080` | + ## Features - **Dashboard** — one card per car: last service, odometer, next-due date/km, and @@ -48,28 +99,3 @@ Login gets a JWT from the API Server (stored client-side) and creates a server session. `auth.js` exposes `isAdmin` and the current profile; the router guards `public` / `admin` routes. Cars are per-user (owned + shared), and the UI mirrors the server's read / write / owner access levels. - -## Structure - -``` -src/ -├── main.js # app bootstrap -├── router.js # /login, / (dashboard), /cars/:id, /settings, /admin -├── api.js # the only place that calls the API Server (base URL resolution) -├── auth.js # session/profile state, isAdmin -├── prefs.js # theme/locale/date/font preferences → -├── lib/format.js # date/km formatting + next-service status badges -├── style.css # Tailwind v4 entry (+ dark custom-variant) -├── App.vue # layout shell + nav (Admin link when admin) -├── components/ -│ ├── Modal.vue CarFormModal.vue ServiceFormModal.vue PartFormModal.vue ShareModal.vue -└── views/ - ├── Login.vue Dashboard.vue CarDetail.vue - └── Settings.vue AdminUsers.vue -``` - -## Build - -```powershell -npm run build # -> dist/ -``` diff --git a/Web App/docker-compose.yml b/Web App/docker-compose.yml deleted file mode 100644 index 8df4b89..0000000 --- a/Web App/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -services: - web-app: - build: - context: . - dockerfile: Dockerfile - args: - # Baked into the bundle at build time. Leave empty to use same-origin - # "/api", which nginx proxies to API_TARGET below. - VITE_API_BASE: "${VITE_API_BASE:-}" - image: drivervault-web - container_name: drivervault-web - restart: unless-stopped - ports: - - "${WEB_PORT:-8081}:80" - environment: - # Upstream API Server that nginx proxies /api/ to. Point this at your - # external API Server (host:port), e.g. http://10.2.1.10:8080. - API_TARGET: "${API_TARGET:-http://api-server:8080}" diff --git a/Web App/nginx.conf.template b/Web App/nginx.conf.template deleted file mode 100644 index 6366697..0000000 --- a/Web App/nginx.conf.template +++ /dev/null @@ -1,35 +0,0 @@ -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - # Compress text assets. - gzip on; - gzip_types text/plain text/css application/javascript application/json image/svg+xml; - gzip_min_length 1024; - - # Proxy API calls to the API Server. The /api prefix is preserved because - # ${API_TARGET} has no path, so /api/me -> $API_TARGET/api/me. - location /api/ { - proxy_pass ${API_TARGET}; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - # Cache fingerprinted build assets aggressively. - location /assets/ { - expires 1y; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } - - # SPA fallback: unknown routes return index.html so the Vue router handles them. - location / { - try_files $uri $uri/ /index.html; - } -} diff --git a/Web App/server/.env.example b/Web App/server/.env.example new file mode 100644 index 0000000..4a29e16 --- /dev/null +++ b/Web App/server/.env.example @@ -0,0 +1,8 @@ +# Web App (BFF) configuration + +# Address the Web App listens on +WEB_ADDR=:8090 + +# Base URL of the API Server. The BFF reverse-proxies /api/* to this host, so +# the browser only ever talks same-origin to the Web App. +API_BASE=http://localhost:8080 diff --git a/Web App/server/.gitignore b/Web App/server/.gitignore new file mode 100644 index 0000000..a88b681 --- /dev/null +++ b/Web App/server/.gitignore @@ -0,0 +1,5 @@ +# Build artifacts (generated) +dist/ +*.exe +.env +*.log diff --git a/Web App/server/Run-WebApp.ps1 b/Web App/server/Run-WebApp.ps1 new file mode 100644 index 0000000..1d530ab --- /dev/null +++ b/Web App/server/Run-WebApp.ps1 @@ -0,0 +1,29 @@ +# Builds the Vue frontend (if needed) and runs the Web App BFF. +# +# ./Run-WebApp.ps1 # build frontend then serve +# ./Run-WebApp.ps1 -SkipBuild # serve existing dist only + +param([switch]$SkipBuild) + +$ErrorActionPreference = "Stop" +$serverDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$root = Split-Path -Parent $serverDir # the "Web App" folder +$webDir = Join-Path $root "web" + +$goBin = "C:\Program Files\Go\bin" +if (Test-Path $goBin) { $env:Path = "$goBin;$env:Path" } + +if (-not $SkipBuild) { + Write-Host "Building frontend..." -ForegroundColor Cyan + Push-Location $webDir + try { + if (-not (Test-Path "node_modules")) { npm install } + npm run build + } finally { Pop-Location } +} + +Push-Location $serverDir +try { + Write-Host "Starting Web App on :8090 (Ctrl+C to stop)..." -ForegroundColor Cyan + go run . +} finally { Pop-Location } diff --git a/Web App/server/go.mod b/Web App/server/go.mod new file mode 100644 index 0000000..d342e07 --- /dev/null +++ b/Web App/server/go.mod @@ -0,0 +1,3 @@ +module drivervault/webapp + +go 1.26 diff --git a/Web App/server/main.go b/Web App/server/main.go new file mode 100644 index 0000000..355fa2c --- /dev/null +++ b/Web App/server/main.go @@ -0,0 +1,113 @@ +// Command webapp is the Web App backend-for-frontend. It serves the embedded +// Vue single-page app and reverse-proxies /api/* to the API Server, so the +// browser only ever talks to this server (same-origin) and all data access +// still flows through the API Server. +package main + +import ( + "embed" + "io/fs" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strings" + "time" +) + +//go:embed all:dist +var distFS embed.FS + +func getenv(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lmsgprefix) + log.SetPrefix("[web] ") + + loadDotEnv(".env") + addr := getenv("WEB_ADDR", ":8090") + apiBase := strings.TrimRight(getenv("API_BASE", "http://localhost:8080"), "/") + + apiURL, err := url.Parse(apiBase) + if err != nil { + log.Fatalf("invalid API_BASE %q: %v", apiBase, err) + } + + // Reverse proxy: /api/* -> API Server (path preserved). + proxy := httputil.NewSingleHostReverseProxy(apiURL) + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, e error) { + log.Printf("proxy error for %s: %v", r.URL.Path, e) + http.Error(w, `{"error":"api server unavailable"}`, http.StatusBadGateway) + } + + // Embedded SPA file server. + sub, err := fs.Sub(distFS, "dist") + if err != nil { + log.Fatalf("embed dist: %v", err) + } + spa := http.FileServer(http.FS(sub)) + + mux := http.NewServeMux() + mux.Handle("/api/", proxy) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Serve static assets when they exist; otherwise fall back to index.html + // so client-side routing works on deep links. + if r.URL.Path != "/" { + if f, err := sub.Open(strings.TrimPrefix(r.URL.Path, "/")); err == nil { + f.Close() + spa.ServeHTTP(w, r) + return + } + } + r2 := r.Clone(r.Context()) + r2.URL.Path = "/" + spa.ServeHTTP(w, r2) + }) + + srv := &http.Server{ + Addr: addr, + Handler: logRequests(mux), + ReadHeaderTimeout: 10 * time.Second, + } + log.Printf("listening on %s (proxying /api -> %s)", addr, apiBase) + if err := srv.ListenAndServe(); err != nil { + log.Fatalf("server error: %v", err) + } +} + +func logRequests(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond)) + }) +} + +// loadDotEnv loads KEY=VALUE pairs from a .env file if present. +func loadDotEnv(path string) { + data, err := os.ReadFile(path) + if err != nil { + return + } + for _, line := range strings.Split(string(data), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + k, v, ok := strings.Cut(line, "=") + if !ok { + continue + } + k = strings.TrimSpace(k) + v = strings.Trim(strings.TrimSpace(v), `"'`) + if _, exists := os.LookupEnv(k); !exists { + _ = os.Setenv(k, v) + } + } +} diff --git a/Web App/web/.gitignore b/Web App/web/.gitignore new file mode 100644 index 0000000..aa0926a --- /dev/null +++ b/Web App/web/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +*.log diff --git a/Web App/index.html b/Web App/web/index.html similarity index 100% rename from Web App/index.html rename to Web App/web/index.html diff --git a/Web App/package-lock.json b/Web App/web/package-lock.json similarity index 100% rename from Web App/package-lock.json rename to Web App/web/package-lock.json diff --git a/Web App/package.json b/Web App/web/package.json similarity index 100% rename from Web App/package.json rename to Web App/web/package.json diff --git a/Web App/public/apple-touch-icon.png b/Web App/web/public/apple-touch-icon.png similarity index 100% rename from Web App/public/apple-touch-icon.png rename to Web App/web/public/apple-touch-icon.png diff --git a/Web App/public/brand/drivervault-appicon-256.png b/Web App/web/public/brand/drivervault-appicon-256.png similarity index 100% rename from Web App/public/brand/drivervault-appicon-256.png rename to Web App/web/public/brand/drivervault-appicon-256.png diff --git a/Web App/public/brand/drivervault-appicon.svg b/Web App/web/public/brand/drivervault-appicon.svg similarity index 100% rename from Web App/public/brand/drivervault-appicon.svg rename to Web App/web/public/brand/drivervault-appicon.svg diff --git a/Web App/public/brand/drivervault-icon-mono.svg b/Web App/web/public/brand/drivervault-icon-mono.svg similarity index 100% rename from Web App/public/brand/drivervault-icon-mono.svg rename to Web App/web/public/brand/drivervault-icon-mono.svg diff --git a/Web App/public/brand/drivervault-icon-white.svg b/Web App/web/public/brand/drivervault-icon-white.svg similarity index 100% rename from Web App/public/brand/drivervault-icon-white.svg rename to Web App/web/public/brand/drivervault-icon-white.svg diff --git a/Web App/public/brand/drivervault-icon.svg b/Web App/web/public/brand/drivervault-icon.svg similarity index 100% rename from Web App/public/brand/drivervault-icon.svg rename to Web App/web/public/brand/drivervault-icon.svg diff --git a/Web App/public/brand/drivervault-lockup-dark.svg b/Web App/web/public/brand/drivervault-lockup-dark.svg similarity index 100% rename from Web App/public/brand/drivervault-lockup-dark.svg rename to Web App/web/public/brand/drivervault-lockup-dark.svg diff --git a/Web App/public/brand/drivervault-lockup.svg b/Web App/web/public/brand/drivervault-lockup.svg similarity index 100% rename from Web App/public/brand/drivervault-lockup.svg rename to Web App/web/public/brand/drivervault-lockup.svg diff --git a/Web App/public/favicon-16.png b/Web App/web/public/favicon-16.png similarity index 100% rename from Web App/public/favicon-16.png rename to Web App/web/public/favicon-16.png diff --git a/Web App/public/favicon-32.png b/Web App/web/public/favicon-32.png similarity index 100% rename from Web App/public/favicon-32.png rename to Web App/web/public/favicon-32.png diff --git a/Web App/public/favicon.svg b/Web App/web/public/favicon.svg similarity index 100% rename from Web App/public/favicon.svg rename to Web App/web/public/favicon.svg diff --git a/Web App/src/App.vue b/Web App/web/src/App.vue similarity index 100% rename from Web App/src/App.vue rename to Web App/web/src/App.vue diff --git a/Web App/src/api.js b/Web App/web/src/api.js similarity index 100% rename from Web App/src/api.js rename to Web App/web/src/api.js diff --git a/Web App/src/auth.js b/Web App/web/src/auth.js similarity index 100% rename from Web App/src/auth.js rename to Web App/web/src/auth.js diff --git a/Web App/src/components/CarFormModal.vue b/Web App/web/src/components/CarFormModal.vue similarity index 100% rename from Web App/src/components/CarFormModal.vue rename to Web App/web/src/components/CarFormModal.vue diff --git a/Web App/src/components/Logo.vue b/Web App/web/src/components/Logo.vue similarity index 100% rename from Web App/src/components/Logo.vue rename to Web App/web/src/components/Logo.vue diff --git a/Web App/src/components/Modal.vue b/Web App/web/src/components/Modal.vue similarity index 100% rename from Web App/src/components/Modal.vue rename to Web App/web/src/components/Modal.vue diff --git a/Web App/src/components/PartFormModal.vue b/Web App/web/src/components/PartFormModal.vue similarity index 100% rename from Web App/src/components/PartFormModal.vue rename to Web App/web/src/components/PartFormModal.vue diff --git a/Web App/src/components/ServiceFormModal.vue b/Web App/web/src/components/ServiceFormModal.vue similarity index 100% rename from Web App/src/components/ServiceFormModal.vue rename to Web App/web/src/components/ServiceFormModal.vue diff --git a/Web App/src/components/ShareModal.vue b/Web App/web/src/components/ShareModal.vue similarity index 100% rename from Web App/src/components/ShareModal.vue rename to Web App/web/src/components/ShareModal.vue diff --git a/Web App/src/lib/format.js b/Web App/web/src/lib/format.js similarity index 100% rename from Web App/src/lib/format.js rename to Web App/web/src/lib/format.js diff --git a/Web App/src/main.js b/Web App/web/src/main.js similarity index 100% rename from Web App/src/main.js rename to Web App/web/src/main.js diff --git a/Web App/src/prefs.js b/Web App/web/src/prefs.js similarity index 100% rename from Web App/src/prefs.js rename to Web App/web/src/prefs.js diff --git a/Web App/src/router.js b/Web App/web/src/router.js similarity index 100% rename from Web App/src/router.js rename to Web App/web/src/router.js diff --git a/Web App/src/style.css b/Web App/web/src/style.css similarity index 100% rename from Web App/src/style.css rename to Web App/web/src/style.css diff --git a/Web App/src/views/AdminUsers.vue b/Web App/web/src/views/AdminUsers.vue similarity index 100% rename from Web App/src/views/AdminUsers.vue rename to Web App/web/src/views/AdminUsers.vue diff --git a/Web App/src/views/CarDetail.vue b/Web App/web/src/views/CarDetail.vue similarity index 100% rename from Web App/src/views/CarDetail.vue rename to Web App/web/src/views/CarDetail.vue diff --git a/Web App/src/views/Dashboard.vue b/Web App/web/src/views/Dashboard.vue similarity index 100% rename from Web App/src/views/Dashboard.vue rename to Web App/web/src/views/Dashboard.vue diff --git a/Web App/src/views/Login.vue b/Web App/web/src/views/Login.vue similarity index 100% rename from Web App/src/views/Login.vue rename to Web App/web/src/views/Login.vue diff --git a/Web App/src/views/Settings.vue b/Web App/web/src/views/Settings.vue similarity index 100% rename from Web App/src/views/Settings.vue rename to Web App/web/src/views/Settings.vue diff --git a/Web App/vite.config.js b/Web App/web/vite.config.js similarity index 69% rename from Web App/vite.config.js rename to Web App/web/vite.config.js index 40110a5..253e6a4 100644 --- a/Web App/vite.config.js +++ b/Web App/web/vite.config.js @@ -2,10 +2,15 @@ import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import tailwindcss from "@tailwindcss/vite"; -// The web app talks ONLY to the API Server. In dev, /api is proxied to it so we -// avoid CORS and can use same-origin relative URLs in the client. +// The web app talks ONLY to the API Server. The production build is written into +// ../server/dist so the Go BFF can embed it. In dev, /api is proxied to the API +// Server so we avoid CORS and can use same-origin relative URLs in the client. export default defineConfig({ plugins: [vue(), tailwindcss()], + build: { + outDir: "../server/dist", + emptyOutDir: true, + }, server: { port: 5173, // Listen on all interfaces so the dev server is reachable on the LAN