feat(web,server): expose server version and commit hash in Profile page

Add version and commit_hash fields to the /ping endpoint response,
sourced from the existing internal/version package (ldflags or
Go build info). The frontend capabilities store reads these values
and displays them as badges at the bottom of the Profile page.
This commit is contained in:
Acbox
2026-03-29 17:38:33 +08:00
parent 13d2f668f5
commit 6c2da4b2f5
10 changed files with 83 additions and 17 deletions
+4 -1
View File
@@ -122,7 +122,10 @@
"themeLight": "Light",
"themeDark": "Dark",
"langZh": "中文",
"langEn": "English"
"langEn": "English",
"version": "Version",
"versionTag": "v{version}",
"commitHash": "Commit"
},
"chat": {
"greeting": "Hi! How can I help you today?",
+4 -1
View File
@@ -118,7 +118,10 @@
"themeLight": "浅色",
"themeDark": "深色",
"langZh": "中文",
"langEn": "English"
"langEn": "English",
"version": "版本",
"versionTag": "v{version}",
"commitHash": "提交"
},
"chat": {
"greeting": "你好!有什么我可以帮你的吗?",
+29 -5
View File
@@ -1,5 +1,5 @@
<template>
<section class="h-full max-w-7xl mx-auto p-4">
<section class="max-w-7xl mx-auto p-4 pb-12">
<div class="max-w-3xl mx-auto space-y-8">
<!-- Avatar & name -->
<div class="flex items-center gap-4">
@@ -26,8 +26,7 @@
<!-- Display Settings -->
<section>
<h2 class="mb-2 flex items-center text-xs font-medium">
<FontAwesomeIcon
:icon="['fas', 'gear']"
<Settings
class="mr-2 size-3.5"
/>
{{ $t('settings.display') }}
@@ -130,8 +129,7 @@
<!-- Linked Channels -->
<section>
<h2 class="mb-2 flex items-center text-xs font-medium">
<FontAwesomeIcon
:icon="['fas', 'network-wired']"
<Network
class="mr-2 size-3.5"
/>
{{ $t('settings.linkedChannels') }}
@@ -190,6 +188,26 @@
@generate="onGenerateBindCode"
@copy="copyBindCode"
/>
<!-- Version -->
<section>
<Separator class="mb-4" />
<div class="flex items-center gap-2 text-xs text-muted-foreground">
<span>{{ $t('settings.version') }}</span>
<Badge
v-if="serverVersion"
variant="secondary"
>
{{ $t('settings.versionTag', { version: serverVersion }) }}
</Badge>
<Badge
v-if="commitHash"
variant="outline"
>
{{ commitHash }}
</Badge>
</div>
</section>
</div>
</section>
</template>
@@ -215,6 +233,7 @@ import { useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { Settings, Network } from 'lucide-vue-next'
import ConfirmPopover from '@/components/confirm-popover/index.vue'
import ProfileSection from './components/profile-section.vue'
import PasswordSection from './components/password-section.vue'
@@ -224,6 +243,7 @@ import { client } from '@memohai/sdk/client'
import type { AccountsAccount, AccountsUpdateProfileRequest, AccountsUpdatePasswordRequest, IdentitiesChannelIdentity } from '@memohai/sdk'
import { useUserStore } from '@/store/user'
import { useSettingsStore } from '@/store/settings'
import { useCapabilitiesStore } from '@/store/capabilities'
import type { Locale } from '@/i18n'
import { resolveApiErrorMessage } from '@/utils/api-error'
import { formatDateTime } from '@/utils/date-time'
@@ -251,6 +271,10 @@ const settingsStore = useSettingsStore()
const { language, theme } = storeToRefs(settingsStore)
const { setLanguage, setTheme } = settingsStore
// ---- Server version ----
const capabilitiesStore = useCapabilitiesStore()
const { serverVersion, commitHash } = storeToRefs(capabilitiesStore)
// ---- User data ----
const account = ref<UserAccount | null>(null)
const identities = ref<IdentitiesChannelIdentity[]>([])
+5 -1
View File
@@ -5,6 +5,8 @@ import { getPing } from '@memohai/sdk'
export const useCapabilitiesStore = defineStore('capabilities', () => {
const containerBackend = ref('containerd')
const snapshotSupported = ref(true)
const serverVersion = ref('')
const commitHash = ref('')
const loaded = ref(false)
async function load() {
@@ -14,6 +16,8 @@ export const useCapabilitiesStore = defineStore('capabilities', () => {
if (data) {
containerBackend.value = data.container_backend ?? 'containerd'
snapshotSupported.value = data.snapshot_supported !== false
serverVersion.value = data.version ?? ''
commitHash.value = data.commit_hash ?? ''
}
} catch {
// fallback: assume containerd
@@ -21,5 +25,5 @@ export const useCapabilitiesStore = defineStore('capabilities', () => {
loaded.value = true
}
return { containerBackend, snapshotSupported, loaded, load }
return { containerBackend, snapshotSupported, serverVersion, commitHash, loaded, load }
})
+5
View File
@@ -7,12 +7,15 @@ import (
"github.com/labstack/echo/v4"
"github.com/memohai/memoh/internal/boot"
"github.com/memohai/memoh/internal/version"
)
type PingResponse struct {
Status string `json:"status"`
ContainerBackend string `json:"container_backend"`
SnapshotSupported bool `json:"snapshot_supported"`
Version string `json:"version"`
CommitHash string `json:"commit_hash"`
}
type PingHandler struct {
@@ -42,6 +45,8 @@ func (h *PingHandler) Ping(c echo.Context) error {
Status: "ok",
ContainerBackend: h.runtime.ContainerBackend,
SnapshotSupported: h.runtime.ContainerBackend != "apple",
Version: version.Version,
CommitHash: version.ShortCommitHash(),
})
}
+19 -10
View File
@@ -17,8 +17,9 @@ var (
BuildTime = ""
)
// GetInfo returns a formatted version string including the version and commit hash.
func GetInfo() string {
// EnsureBuildInfo populates CommitHash and BuildTime from Go build info
// when they were not set via ldflags.
func EnsureBuildInfo() {
if CommitHash == "" {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
@@ -31,15 +32,23 @@ func GetInfo() string {
}
}
}
res := Version
if CommitHash != "" {
// Only use the first 7 characters of the commit hash if it's long
shortHash := CommitHash
if len(shortHash) > 7 {
shortHash = shortHash[:7]
}
res += fmt.Sprintf(" (%s)", shortHash)
// ShortCommitHash returns the first 7 characters of the commit hash.
func ShortCommitHash() string {
EnsureBuildInfo()
if len(CommitHash) > 7 {
return CommitHash[:7]
}
return CommitHash
}
// GetInfo returns a formatted version string including the version and commit hash.
func GetInfo() string {
EnsureBuildInfo()
res := Version
if h := ShortCommitHash(); h != "" {
res += fmt.Sprintf(" (%s)", h)
}
return res
}
+2
View File
@@ -878,9 +878,11 @@ export type HandlersModelTokenUsage = {
};
export type HandlersPingResponse = {
commit_hash?: string;
container_backend?: string;
snapshot_supported?: boolean;
status?: string;
version?: string;
};
export type HandlersProbeResponse = {
+6
View File
@@ -10960,6 +10960,9 @@ const docTemplate = `{
"handlers.PingResponse": {
"type": "object",
"properties": {
"commit_hash": {
"type": "string"
},
"container_backend": {
"type": "string"
},
@@ -10968,6 +10971,9 @@ const docTemplate = `{
},
"status": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
+6
View File
@@ -10951,6 +10951,9 @@
"handlers.PingResponse": {
"type": "object",
"properties": {
"commit_hash": {
"type": "string"
},
"container_backend": {
"type": "string"
},
@@ -10959,6 +10962,9 @@
},
"status": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
+4
View File
@@ -1445,12 +1445,16 @@ definitions:
type: object
handlers.PingResponse:
properties:
commit_hash:
type: string
container_backend:
type: string
snapshot_supported:
type: boolean
status:
type: string
version:
type: string
type: object
handlers.ProbeResponse:
properties: