feature/version-badge #40

Merged
jason merged 4 commits from feature/version-badge into master 2026-03-08 00:56:18 -06:00
4 changed files with 60 additions and 7 deletions

View File

@@ -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"]

View File

@@ -0,0 +1,5 @@
{
"sha": "dev",
"shortSha": "dev",
"buildTime": null
}

View File

@@ -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 (
<>
<style>{`
@@ -53,13 +58,27 @@ function AppFooter() {
}
`}</style>
<footer style={sf.footer}>
<span style={sf.copy}>© {year} Jason Stedwell</span>
<span style={sf.sep}>·</span>
<span style={sf.copy}>&copy; {year} Jason Stedwell</span>
<span style={sf.sep}>&middot;</span>
<DevTicker />
<span style={sf.sep}>·</span>
<span style={sf.sep}>&middot;</span>
<a href={REPO_URL} target="_blank" rel="noopener noreferrer" style={sf.link}>
<GiteaIcon /> cpas
</a>
{sha && sha !== 'dev' && (
<>
<span style={sf.sep}>&middot;</span>
<a
href={`${REPO_URL}/commit/${version.sha}`}
target="_blank"
rel="noopener noreferrer"
style={sf.link}
title={built ? `Built ${built}` : 'View commit'}
>
{sha}
</a>
</>
)}
</footer>
</>
);
@@ -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 (
<ToastProvider>
@@ -156,7 +183,7 @@ export default function App() {
</div>
</div>
<AppFooter />
<AppFooter version={version} />
{showReadme && <ReadmeModal onClose={() => setShowReadme(false)} />}
</div>

View File

@@ -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) => {