show version in UI

This commit is contained in:
2026-03-09 09:43:13 +11:00
parent c796f1324e
commit 76851f0816
4 changed files with 107 additions and 6 deletions

View File

@@ -12,9 +12,16 @@ RUN go mod download
# Copy the rest of the source
COPY . .
ARG BUILD_TIME=""
ARG GIT_COMMIT="unknown"
ARG VERSION="dev"
# Build a static-ish binary (alpine musl)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o /out/ingestd ./cmd/ingestd
RUN BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath \
-ldflags="-s -w -X main.buildTime=${BUILD_TIME} -X main.gitCommit=${GIT_COMMIT} -X main.version=${VERSION}" \
-o /out/ingestd ./cmd/ingestd
# ---- runtime stage ----
FROM alpine:3.22

59
cmd/ingestd/buildinfo.go Normal file
View File

@@ -0,0 +1,59 @@
package main
import (
"runtime/debug"
"strings"
)
var (
version = "dev"
gitCommit = "unknown"
buildTime = "unknown"
)
type binaryBuildInfo struct {
Version string `json:"version"`
GitCommit string `json:"git_commit"`
BuildTime string `json:"build_time"`
}
func currentBuildInfo() binaryBuildInfo {
out := binaryBuildInfo{
Version: version,
GitCommit: gitCommit,
BuildTime: buildTime,
}
if bi, ok := debug.ReadBuildInfo(); ok {
if out.Version == "" || out.Version == "dev" {
if bi.Main.Version != "" && bi.Main.Version != "(devel)" {
out.Version = bi.Main.Version
}
}
for _, s := range bi.Settings {
if s.Key == "vcs.revision" && (out.GitCommit == "" || out.GitCommit == "unknown") {
if len(s.Value) > 12 {
out.GitCommit = s.Value[:12]
} else {
out.GitCommit = s.Value
}
}
if s.Key == "vcs.time" && (out.BuildTime == "" || out.BuildTime == "unknown") {
out.BuildTime = s.Value
}
}
}
out.Version = normalizeBuildField(out.Version, "dev")
out.GitCommit = normalizeBuildField(out.GitCommit, "unknown")
out.BuildTime = normalizeBuildField(out.BuildTime, "unknown")
return out
}
func normalizeBuildField(v string, fallback string) string {
v = strings.TrimSpace(v)
if v == "" {
return fallback
}
return v
}

View File

@@ -21,12 +21,14 @@ type webServer struct {
db *db.DB
site providers.Site
model string
build binaryBuildInfo
}
type dashboardResponse struct {
GeneratedAt time.Time `json:"generated_at"`
Site string `json:"site"`
Model string `json:"model"`
Build binaryBuildInfo `json:"build"`
RangeStart time.Time `json:"range_start"`
RangeEnd time.Time `json:"range_end"`
Observations []db.ObservationPoint `json:"observations"`
@@ -46,6 +48,7 @@ func runWebServer(ctx context.Context, d *db.DB, site providers.Site, model, add
db: d,
site: site,
model: model,
build: currentBuildInfo(),
}
mux := http.NewServeMux()
@@ -53,7 +56,10 @@ func runWebServer(ctx context.Context, d *db.DB, site providers.Site, model, add
mux.HandleFunc("/api/dashboard", ws.handleDashboard)
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ok":true}`))
_ = json.NewEncoder(w).Encode(map[string]any{
"ok": true,
"build": ws.build,
})
})
mux.HandleFunc("/chart", func(w http.ResponseWriter, r *http.Request) {
serveIndex(w, sub)
@@ -193,6 +199,7 @@ func (s *webServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
GeneratedAt: time.Now().UTC(),
Site: s.site.Name,
Model: s.model,
Build: s.build,
RangeStart: start,
RangeEnd: end,
Observations: observations,

View File

@@ -615,7 +615,35 @@ function extendForecastTo(points, endTime) {
return [...points, { ...last, ts: new Date(endTs).toISOString() }];
}
function updateSiteMeta(site, model, tzLabel) {
function formatBuildStamp(build) {
if (!build) {
return "build --";
}
const parts = [];
const rawBuildTime = build.build_time;
if (rawBuildTime && rawBuildTime !== "unknown") {
const dt = new Date(rawBuildTime);
if (Number.isNaN(dt.getTime())) {
parts.push(rawBuildTime);
} else {
parts.push(formatDateTime(dt));
}
} else {
parts.push("--");
}
if (build.git_commit && build.git_commit !== "unknown") {
parts.push(`commit ${build.git_commit}`);
}
if (build.version && build.version !== "dev") {
parts.push(`version ${build.version}`);
}
return `build ${parts.join(", ")}`;
}
function updateSiteMeta(site, model, tzLabel, build) {
const home = document.getElementById("site-home");
const suffix = document.getElementById("site-meta-suffix");
if (home) {
@@ -623,7 +651,7 @@ function updateSiteMeta(site, model, tzLabel) {
home.setAttribute("href", "/");
}
if (suffix) {
suffix.textContent = ` | model ${model || "--"} | ${tzLabel}`;
suffix.textContent = ` | model ${model || "--"} | ${tzLabel} | ${formatBuildStamp(build)}`;
}
}
@@ -686,7 +714,7 @@ function baseOptions(range) {
function renderDashboard(data) {
const latest = data.latest;
const tzLabel = state.tz === "utc" ? "UTC" : "Local";
updateSiteMeta(data.site, data.model, tzLabel);
updateSiteMeta(data.site, data.model, tzLabel, data.build);
updateText("last-updated", `updated ${formatDateTime(data.generated_at)}`);
const forecastMeta = data.forecast && data.forecast.points && data.forecast.points.length
? `forecast retrieved ${formatDateTime(data.forecast.retrieved_at)}`