diff --git a/Dockerfile b/Dockerfile
index 18091ea..5aa27ad 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,15 @@ RUN cd client && npm install
COPY client/ ./client/
RUN cd client && npm run build
+# ── Version metadata ──────────────────────────────────────────────────────────
+# Pass these at build time:
+# docker build --build-arg GIT_SHA=$(git rev-parse HEAD) \
+# --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) .
+ARG GIT_SHA=dev
+ARG BUILD_TIME=unknown
+RUN echo "{\"sha\":\"${GIT_SHA}\",\"shortSha\":\"${GIT_SHA:0:7}\",\"buildTime\":\"${BUILD_TIME}\"}" \
+ > /build/client/dist/version.json
+
FROM node:20-alpine AS production
RUN apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
@@ -25,5 +34,6 @@ COPY demo/ ./demo/
COPY client/public/static ./client/dist/static
RUN mkdir -p /data
EXPOSE 3001
-HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD wget -qO- http://localhost:3001/api/health || exit 1
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
+ CMD wget -qO- http://localhost:3001/api/health || exit 1
CMD ["node", "server.js"]
diff --git a/client/public/version.json b/client/public/version.json
new file mode 100644
index 0000000..d23d125
--- /dev/null
+++ b/client/public/version.json
@@ -0,0 +1,5 @@
+{
+ "sha": "dev",
+ "shortSha": "dev",
+ "buildTime": null
+}
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 37cb06d..0055663 100755
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -42,8 +42,13 @@ function GiteaIcon() {
);
}
-function AppFooter() {
+function AppFooter({ version }) {
const year = new Date().getFullYear();
+ const sha = version?.shortSha || null;
+ const built = version?.buildTime
+ ? new Date(version.buildTime).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ : null;
+
return (
<>
>
);
@@ -129,6 +148,14 @@ const sf = {
export default function App() {
const [tab, setTab] = useState('dashboard');
const [showReadme, setShowReadme] = useState(false);
+ const [version, setVersion] = useState(null);
+
+ useEffect(() => {
+ fetch('/version.json')
+ .then(r => r.ok ? r.json() : null)
+ .then(v => { if (v) setVersion(v); })
+ .catch(() => {});
+ }, []);
return (
@@ -156,7 +183,7 @@ export default function App() {
-
+
{showReadme && setShowReadme(false)} />}
diff --git a/server.js b/server.js
index d1dc66b..e4a220e 100755
--- a/server.js
+++ b/server.js
@@ -29,8 +29,19 @@ function audit(action, entityType, entityId, performedBy, details) {
}
}
+// ── Version info (written by Dockerfile at build time) ───────────────────────
+// Falls back to { sha: 'dev' } when running outside a Docker build (local dev).
+let BUILD_VERSION = { sha: 'dev', shortSha: 'dev', buildTime: null };
+try {
+ BUILD_VERSION = require('./client/dist/version.json');
+} catch (_) { /* pre-build or local dev — stub values are fine */ }
+
// Health
-app.get('/api/health', (req, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() }));
+app.get('/api/health', (req, res) => res.json({
+ status: 'ok',
+ timestamp: new Date().toISOString(),
+ version: BUILD_VERSION,
+}));
// ── Employees ────────────────────────────────────────────────────────────────
app.get('/api/employees', (req, res) => {