fix(frontend): admin custom menu items not showing in sidebar

The public settings API filters out menu items with visibility='admin',
so customMenuItemsForAdmin was always empty when reading from
cachedPublicSettings. Fix by loading custom menu items from the admin
settings API (via adminSettingsStore) which returns all items unfiltered.

Changes:
- adminSettings store: store custom_menu_items from admin settings API
- AppSidebar: read admin menu items from adminSettingsStore instead of
  cachedPublicSettings
- CustomPageView: merge public and admin menu items so admin users can
  access admin-only custom pages
This commit is contained in:
erio
2026-03-03 10:45:35 +08:00
parent b7df7ce5d5
commit 5ba71cd2f1
3 changed files with 33 additions and 9 deletions

View File

@@ -579,7 +579,7 @@ const customMenuItemsForUser = computed(() => {
})
const customMenuItemsForAdmin = computed(() => {
const items = appStore.cachedPublicSettings?.custom_menu_items ?? []
const items = adminSettingsStore.customMenuItems ?? []
return items
.filter((item) => item.visibility === 'admin')
.sort((a, b) => a.sort_order - b.sort_order)

View File

@@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { adminAPI } from '@/api'
import type { CustomMenuItem } from '@/types'
export const useAdminSettingsStore = defineStore('adminSettings', () => {
const loaded = ref(false)
@@ -43,6 +44,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', () => {
}
}
// Custom menu items (all items including admin-only, loaded from admin settings API)
const customMenuItems = ref<CustomMenuItem[]>([])
// Default open, but honor cached value to reduce UI flicker on first paint.
const opsMonitoringEnabled = ref(readCachedBool('ops_monitoring_enabled_cached', true))
const opsRealtimeMonitoringEnabled = ref(readCachedBool('ops_realtime_monitoring_enabled_cached', true))
@@ -64,6 +68,8 @@ export const useAdminSettingsStore = defineStore('adminSettings', () => {
opsQueryModeDefault.value = settings.ops_query_mode_default || 'auto'
writeCachedString('ops_query_mode_default_cached', opsQueryModeDefault.value)
customMenuItems.value = settings.custom_menu_items ?? []
loaded.value = true
} catch (err) {
// Keep cached/default value: do not "flip" the UI based on a transient fetch failure.
@@ -122,6 +128,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', () => {
opsMonitoringEnabled,
opsRealtimeMonitoringEnabled,
opsQueryModeDefault,
customMenuItems,
fetch,
setOpsMonitoringEnabledLocal,
setOpsRealtimeMonitoringEnabledLocal,

View File

@@ -70,6 +70,7 @@ import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores'
import { useAuthStore } from '@/stores/auth'
import { useAdminSettingsStore } from '@/stores/adminSettings'
import AppLayout from '@/components/layout/AppLayout.vue'
import Icon from '@/components/icons/Icon.vue'
import { buildEmbeddedUrl, detectTheme } from '@/utils/embedded-url'
@@ -78,6 +79,7 @@ const { t } = useI18n()
const route = useRoute()
const appStore = useAppStore()
const authStore = useAuthStore()
const adminSettingsStore = useAdminSettingsStore()
const loading = ref(false)
const pageTheme = ref<'light' | 'dark'>('light')
@@ -86,8 +88,15 @@ let themeObserver: MutationObserver | null = null
const menuItemId = computed(() => route.params.id as string)
const menuItem = computed(() => {
const items = appStore.cachedPublicSettings?.custom_menu_items ?? []
const found = items.find((item) => item.id === menuItemId.value) ?? null
const publicItems = appStore.cachedPublicSettings?.custom_menu_items ?? []
const adminItems = authStore.isAdmin ? (adminSettingsStore.customMenuItems ?? []) : []
const allItems = [...publicItems]
for (const item of adminItems) {
if (!allItems.some((existing) => existing.id === item.id)) {
allItems.push(item)
}
}
const found = allItems.find((item) => item.id === menuItemId.value) ?? null
if (found && found.visibility === 'admin' && !authStore.isAdmin) {
return null
}
@@ -122,13 +131,21 @@ onMounted(async () => {
})
}
if (appStore.publicSettingsLoaded) return
const promises: Promise<unknown>[] = []
if (!appStore.publicSettingsLoaded) {
promises.push(appStore.fetchPublicSettings())
}
if (authStore.isAdmin) {
promises.push(adminSettingsStore.fetch())
}
if (promises.length > 0) {
loading.value = true
try {
await appStore.fetchPublicSettings()
await Promise.all(promises)
} finally {
loading.value = false
}
}
})
onUnmounted(() => {