Add auto-incrementing version display to web UI footer

Exposes GET /version endpoint in the web container (reads APP_VERSION env var).
CI computes the version as 1.0.<git-commit-count> and passes it via --build-arg at build time.
Both index.html and cv-matcher/index.html show the version in the footer via a JS fetch.
docker-compose passes APP_VERSION through to the running container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 20:18:31 +03:00
parent eced9531bc
commit 0154b56881
9 changed files with 30 additions and 1 deletions
+4 -1
View File
@@ -47,7 +47,10 @@ jobs:
- name: Build Web image - name: Build Web image
run: | run: |
docker build -f web/Dockerfile -t "${REGISTRY_HOST}/${WEB_IMAGE}:${IMAGE_TAG}" . APP_VERSION="1.0.$(git rev-list --count HEAD)"
docker build -f web/Dockerfile \
--build-arg APP_VERSION="${APP_VERSION}" \
-t "${REGISTRY_HOST}/${WEB_IMAGE}:${IMAGE_TAG}" .
- name: Build CV cleanup job image - name: Build CV cleanup job image
run: | run: |
+4
View File
@@ -7,6 +7,10 @@
# For local dev this is ignored (docker-compose.override.yml builds images locally). # For local dev this is ignored (docker-compose.override.yml builds images locally).
IMAGE_TAG=staging IMAGE_TAG=staging
# Application version displayed in the web UI footer.
# CI sets this automatically to 1.0.<git-commit-count> at build time.
APP_VERSION=1.0.0
# Volume base paths — controls where logs and uploaded files are stored on the host. # Volume base paths — controls where logs and uploaded files are stored on the host.
# Portainer (staging/prod): leave unset to use the /opt/myai defaults. # Portainer (staging/prod): leave unset to use the /opt/myai defaults.
# Local dev: set to relative paths so logs and files land in the repo tree. # Local dev: set to relative paths so logs and files land in the repo tree.
+1
View File
@@ -263,6 +263,7 @@ services:
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging} - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging}
- APP_VERSION=${APP_VERSION:-unknown}
- Site__Mode=${Site__Mode:-Normal} - Site__Mode=${Site__Mode:-Normal}
networks: networks:
- myai-network - myai-network
+2
View File
@@ -13,6 +13,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app WORKDIR /app
EXPOSE 8080 EXPOSE 8080
ENV ASPNETCORE_URLS=http://0.0.0.0:8080 ENV ASPNETCORE_URLS=http://0.0.0.0:8080
ARG APP_VERSION=1.0.0
ENV APP_VERSION=${APP_VERSION}
COPY --from=build /app/publish . COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "web.dll"] ENTRYPOINT ["dotnet", "web.dll"]
+3
View File
@@ -14,6 +14,9 @@ var app = builder.Build();
app.UseMiddleware<SiteModeMiddleware>(); app.UseMiddleware<SiteModeMiddleware>();
app.MapGet("/version", (IConfiguration config) =>
Results.Json(new { version = config["APP_VERSION"] ?? "unknown" }));
// Static site // Static site
app.UseDefaultFiles(); app.UseDefaultFiles();
app.UseStaticFiles(); app.UseStaticFiles();
+7
View File
@@ -607,6 +607,13 @@ img {
flex-wrap: wrap flex-wrap: wrap
} }
.app-version {
font-size: .7rem;
color: var(--muted);
opacity: .5;
font-family: monospace
}
.cookie-overlay { .cookie-overlay {
position: fixed; position: fixed;
left: 0; left: 0;
+1
View File
@@ -186,6 +186,7 @@
<a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a> <a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a>
<a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a> <a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a>
</div> </div>
<span id="app-version" class="app-version"></span>
<a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a> <a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a>
</div> </div>
</footer> </footer>
+1
View File
@@ -189,6 +189,7 @@
<a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a> <a data-legal="privacy" href="/legal/privacy-en.html" target="_blank" data-i18n="legal.privacy">Privacy</a>
<a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a> <a data-legal="cookies" href="/legal/cookies-en.html" target="_blank" data-i18n="legal.cookies">Cookies</a>
</div> </div>
<span id="app-version" class="app-version"></span>
<a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a> <a href="#top" class="back-to-top btn btn-dark btn-sm shadow" data-i18n="footer.top">Back to top</a>
</div> </div>
</footer> </footer>
+7
View File
@@ -262,6 +262,13 @@
} }
$('#year').text(new Date().getFullYear()); $('#year').text(new Date().getFullYear());
$.getJSON('/version').done(function (data) {
if (data && data.version) {
$('#app-version').text('v' + data.version);
}
});
applyLanguage(currentLang()); applyLanguage(currentLang());
$('.lang-flag').on('click', function () { $('.lang-flag').on('click', function () {
applyLanguage($(this).data('lang')); applyLanguage($(this).data('lang'));