From 0083f6a510a47568dd48b62def7af5c91880fc40 Mon Sep 17 00:00:00 2001 From: Kelly Date: Sun, 30 Nov 2025 10:01:10 -0700 Subject: [PATCH] Add version display in admin sidebar footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /api/version endpoint that returns build info from env vars - Add version footer to Layout.tsx showing build version, git SHA, and image tag - Update Dockerfile to accept build args for version info (APP_BUILD_VERSION, APP_GIT_SHA, APP_BUILD_TIME, CONTAINER_IMAGE_TAG) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/Dockerfile | 12 ++++++++++ backend/src/index.ts | 2 ++ backend/src/routes/version.ts | 25 +++++++++++++++++++++ frontend/src/components/Layout.tsx | 35 +++++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 backend/src/routes/version.ts diff --git a/backend/Dockerfile b/backend/Dockerfile index 8e9a8d3e..047904a7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,6 +12,18 @@ RUN npm run build # Production stage FROM node:20-slim +# Build arguments for version info +ARG APP_BUILD_VERSION=dev +ARG APP_GIT_SHA=unknown +ARG APP_BUILD_TIME=unknown +ARG CONTAINER_IMAGE_TAG=local + +# Set version info as environment variables +ENV APP_BUILD_VERSION=${APP_BUILD_VERSION} +ENV APP_GIT_SHA=${APP_GIT_SHA} +ENV APP_BUILD_TIME=${APP_BUILD_TIME} +ENV CONTAINER_IMAGE_TAG=${CONTAINER_IMAGE_TAG} + # Install Chromium dependencies RUN apt-get update && apt-get install -y \ chromium \ diff --git a/backend/src/index.ts b/backend/src/index.ts index f63e372b..8f1abbba 100755 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -51,6 +51,7 @@ import apiPermissionsRoutes from './routes/api-permissions'; import parallelScrapeRoutes from './routes/parallel-scrape'; import scheduleRoutes from './routes/schedule'; import crawlerSandboxRoutes from './routes/crawler-sandbox'; +import versionRoutes from './routes/version'; import { trackApiUsage, checkRateLimit } from './middleware/apiTokenTracker'; import { startCrawlScheduler } from './services/crawl-scheduler'; import { validateWordPressPermissions } from './middleware/wordpressPermissions'; @@ -80,6 +81,7 @@ app.use('/api/api-permissions', apiPermissionsRoutes); app.use('/api/parallel-scrape', parallelScrapeRoutes); app.use('/api/schedule', scheduleRoutes); app.use('/api/crawler-sandbox', crawlerSandboxRoutes); +app.use('/api/version', versionRoutes); async function startServer() { try { diff --git a/backend/src/routes/version.ts b/backend/src/routes/version.ts new file mode 100644 index 00000000..1ef1d3de --- /dev/null +++ b/backend/src/routes/version.ts @@ -0,0 +1,25 @@ +import { Router, Request, Response } from 'express'; + +const router = Router(); + +/** + * GET /api/version + * Returns build version information for display in admin UI + */ +router.get('/', async (req: Request, res: Response) => { + try { + const versionInfo = { + build_version: process.env.APP_BUILD_VERSION || 'dev', + git_sha: process.env.APP_GIT_SHA || 'local', + build_time: process.env.APP_BUILD_TIME || new Date().toISOString(), + image_tag: process.env.CONTAINER_IMAGE_TAG || 'local', + }; + + res.json(versionInfo); + } catch (error: any) { + console.error('Error fetching version info:', error); + res.status(500).json({ error: 'Failed to fetch version info' }); + } +}); + +export default router; diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index b3079cc9..b43d5e08 100755 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,6 +1,7 @@ -import { ReactNode } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useAuthStore } from '../store/authStore'; +import { api } from '../lib/api'; import { LayoutDashboard, Store, @@ -24,6 +25,13 @@ interface LayoutProps { children: ReactNode; } +interface VersionInfo { + build_version: string; + git_sha: string; + build_time: string; + image_tag: string; +} + interface NavLinkProps { to: string; icon: ReactNode; @@ -69,6 +77,19 @@ export function Layout({ children }: LayoutProps) { const navigate = useNavigate(); const location = useLocation(); const { user, logout } = useAuthStore(); + const [versionInfo, setVersionInfo] = useState(null); + + useEffect(() => { + const fetchVersion = async () => { + try { + const response = await api.get('/version'); + setVersionInfo(response.data); + } catch (error) { + console.error('Failed to fetch version info:', error); + } + }; + fetchVersion(); + }, []); const handleLogout = () => { logout(); @@ -206,6 +227,18 @@ export function Layout({ children }: LayoutProps) { Logout + + {/* Version Footer */} + {versionInfo && ( +
+

+ {versionInfo.build_version} ({versionInfo.git_sha.slice(0, 7)}) +

+

+ {versionInfo.image_tag} +

+
+ )} {/* Main Content */}