show version in UI
This commit is contained in:
11
Dockerfile
11
Dockerfile
@@ -12,9 +12,16 @@ RUN go mod download
|
|||||||
# Copy the rest of the source
|
# Copy the rest of the source
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ARG BUILD_TIME=""
|
||||||
|
ARG GIT_COMMIT="unknown"
|
||||||
|
ARG VERSION="dev"
|
||||||
|
|
||||||
# Build a static-ish binary (alpine musl)
|
# Build a static-ish binary (alpine musl)
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
RUN BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
|
||||||
go build -trimpath -ldflags="-s -w" -o /out/ingestd ./cmd/ingestd
|
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 ----
|
# ---- runtime stage ----
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|||||||
59
cmd/ingestd/buildinfo.go
Normal file
59
cmd/ingestd/buildinfo.go
Normal 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
|
||||||
|
}
|
||||||
@@ -21,12 +21,14 @@ type webServer struct {
|
|||||||
db *db.DB
|
db *db.DB
|
||||||
site providers.Site
|
site providers.Site
|
||||||
model string
|
model string
|
||||||
|
build binaryBuildInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type dashboardResponse struct {
|
type dashboardResponse struct {
|
||||||
GeneratedAt time.Time `json:"generated_at"`
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
Site string `json:"site"`
|
Site string `json:"site"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
|
Build binaryBuildInfo `json:"build"`
|
||||||
RangeStart time.Time `json:"range_start"`
|
RangeStart time.Time `json:"range_start"`
|
||||||
RangeEnd time.Time `json:"range_end"`
|
RangeEnd time.Time `json:"range_end"`
|
||||||
Observations []db.ObservationPoint `json:"observations"`
|
Observations []db.ObservationPoint `json:"observations"`
|
||||||
@@ -46,6 +48,7 @@ func runWebServer(ctx context.Context, d *db.DB, site providers.Site, model, add
|
|||||||
db: d,
|
db: d,
|
||||||
site: site,
|
site: site,
|
||||||
model: model,
|
model: model,
|
||||||
|
build: currentBuildInfo(),
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
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/dashboard", ws.handleDashboard)
|
||||||
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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) {
|
mux.HandleFunc("/chart", func(w http.ResponseWriter, r *http.Request) {
|
||||||
serveIndex(w, sub)
|
serveIndex(w, sub)
|
||||||
@@ -193,6 +199,7 @@ func (s *webServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
GeneratedAt: time.Now().UTC(),
|
GeneratedAt: time.Now().UTC(),
|
||||||
Site: s.site.Name,
|
Site: s.site.Name,
|
||||||
Model: s.model,
|
Model: s.model,
|
||||||
|
Build: s.build,
|
||||||
RangeStart: start,
|
RangeStart: start,
|
||||||
RangeEnd: end,
|
RangeEnd: end,
|
||||||
Observations: observations,
|
Observations: observations,
|
||||||
|
|||||||
@@ -615,7 +615,35 @@ function extendForecastTo(points, endTime) {
|
|||||||
return [...points, { ...last, ts: new Date(endTs).toISOString() }];
|
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 home = document.getElementById("site-home");
|
||||||
const suffix = document.getElementById("site-meta-suffix");
|
const suffix = document.getElementById("site-meta-suffix");
|
||||||
if (home) {
|
if (home) {
|
||||||
@@ -623,7 +651,7 @@ function updateSiteMeta(site, model, tzLabel) {
|
|||||||
home.setAttribute("href", "/");
|
home.setAttribute("href", "/");
|
||||||
}
|
}
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
suffix.textContent = ` | model ${model || "--"} | ${tzLabel}`;
|
suffix.textContent = ` | model ${model || "--"} | ${tzLabel} | ${formatBuildStamp(build)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +714,7 @@ function baseOptions(range) {
|
|||||||
function renderDashboard(data) {
|
function renderDashboard(data) {
|
||||||
const latest = data.latest;
|
const latest = data.latest;
|
||||||
const tzLabel = state.tz === "utc" ? "UTC" : "Local";
|
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)}`);
|
updateText("last-updated", `updated ${formatDateTime(data.generated_at)}`);
|
||||||
const forecastMeta = data.forecast && data.forecast.points && data.forecast.points.length
|
const forecastMeta = data.forecast && data.forecast.points && data.forecast.points.length
|
||||||
? `forecast retrieved ${formatDateTime(data.forecast.retrieved_at)}`
|
? `forecast retrieved ${formatDateTime(data.forecast.retrieved_at)}`
|
||||||
|
|||||||
Reference in New Issue
Block a user