From 5ba71cd2f1a6ab7d08f85b03ba68243b76e648e1 Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 3 Mar 2026 10:45:35 +0800 Subject: [PATCH] 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 --- frontend/src/components/layout/AppSidebar.vue | 2 +- frontend/src/stores/adminSettings.ts | 7 ++++ frontend/src/views/user/CustomPageView.vue | 33 ++++++++++++++----- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue index dcfc60bbb8..f6e31f3697 100644 --- a/frontend/src/components/layout/AppSidebar.vue +++ b/frontend/src/components/layout/AppSidebar.vue @@ -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) diff --git a/frontend/src/stores/adminSettings.ts b/frontend/src/stores/adminSettings.ts index 460cc92b24..3696b560b3 100644 --- a/frontend/src/stores/adminSettings.ts +++ b/frontend/src/stores/adminSettings.ts @@ -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([]) + // 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, diff --git a/frontend/src/views/user/CustomPageView.vue b/frontend/src/views/user/CustomPageView.vue index ed1c11d78d..daea29f105 100644 --- a/frontend/src/views/user/CustomPageView.vue +++ b/frontend/src/views/user/CustomPageView.vue @@ -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,12 +131,20 @@ onMounted(async () => { }) } - if (appStore.publicSettingsLoaded) return - loading.value = true - try { - await appStore.fetchPublicSettings() - } finally { - loading.value = false + const promises: Promise[] = [] + if (!appStore.publicSettingsLoaded) { + promises.push(appStore.fetchPublicSettings()) + } + if (authStore.isAdmin) { + promises.push(adminSettingsStore.fetch()) + } + if (promises.length > 0) { + loading.value = true + try { + await Promise.all(promises) + } finally { + loading.value = false + } } })