From 404316140f722417234dc2f16bb69aecbadc27bb Mon Sep 17 00:00:00 2001 From: "M@" Date: Thu, 4 Dec 2025 15:03:49 -0500 Subject: [PATCH] almost working some of the time --- conf/sample.ini | 6 ++ pkg/api/index.go | 21 ++++ pkg/setting/setting.go | 4 + public/scripts/set-default-sidebar.js | 138 ++++++++++++++++++++------ 4 files changed, 139 insertions(+), 30 deletions(-) diff --git a/conf/sample.ini b/conf/sample.ini index d6397f894e4..e9afafb748c 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1991,6 +1991,12 @@ default_datasource_uid = ;feature1 = true ;feature2 = false +# default for the navigation sidebar docking behavior +# true = docked by default +# false = undocked by default +# When unspecified the current Grafana default remains (true). +;default_sidebar_docked = false + [date_formats] # For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/ diff --git a/pkg/api/index.go b/pkg/api/index.go index 197a83d1013..047c780bb7d 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -220,6 +220,27 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV hs.HooksService.RunIndexDataHooks(&data, c) + if data.Settings.FeatureToggles == nil { + data.Settings.FeatureToggles = map[string]bool{} + } + + // Use value populated by your loader (pkg/setting/feature_toggles.go). If not wired, + // you can temporarily hardcode here for testing or read env. + data.Settings.FeatureToggles["default_sidebar_docked"] = setting.FeatureToggleConfig.DefaultSidebarDocked + + // Example temporary hardcode for quick verification: + // data.Settings.FeatureToggles["default_sidebar_docked"] = false + + // Instrumentation: log the runtime value so you can see where it's coming from + if c != nil { + c.Logger.Info("feature toggle value", "default_sidebar_docked", setting.FeatureToggleConfig.DefaultSidebarDocked) + } else if hs != nil && hs.log != nil { + hs.log.Info("feature toggle value", "default_sidebar_docked", setting.FeatureToggleConfig.DefaultSidebarDocked) + } else { + // quick-and-dirty fallback (will print to stdout) + fmt.Printf("feature toggle default_sidebar_docked=%v\n", setting.FeatureToggleConfig.DefaultSidebarDocked) + } + data.NavTree.Sort() return &data, nil diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index f6c0b3d3f19..f7a6cef2ab2 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1019,6 +1019,10 @@ func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) { // apply command line overrides cfg.applyCommandLineProperties(commandLineProps, parsedFile) + // --- ADD THIS LINE TO LOAD FEATURE TOGGLES FROM THE FINAL PARSED INI --- + loadFeatureToggles(parsedFile) + // + // evaluate config values containing environment variables err = expandConfig(parsedFile) if err != nil { diff --git a/public/scripts/set-default-sidebar.js b/public/scripts/set-default-sidebar.js index 877c93393b7..fe85b74a1bb 100644 --- a/public/scripts/set-default-sidebar.js +++ b/public/scripts/set-default-sidebar.js @@ -1,43 +1,121 @@ -// This script runs very early (before frontend bundles) so we cannot import @grafana/data here. -// Disable the lint rule that forbids direct localStorage usage for this file. /* eslint-disable no-restricted-syntax */ - +// Early script to initialize grafana.navigation.docked from server bootdata. +// Behavior: +// - If the user already has a preference (no companion ".auto" key) we never override it. +// - If we set the value automatically, we set a companion key to mark it. +// - If later the authoritative bootdata promise resolves, we will override only if +// the current value was previously auto-set by us. (function () { try { let key = 'grafana.navigation.docked'; + let autoKey = key + '.auto'; - // If already set (user choice exists), do nothing. - if (localStorage.getItem(key) !== null) { - return; + function isAutoSet() { + try { + return localStorage.getItem(autoKey) === '1'; + } catch (e) { + return false; + } } - // Try the server-provided boot data (common Grafana pattern) - let serverDefault; - if ( - typeof window !== 'undefined' && - window.grafanaBootData && - window.grafanaBootData.settings && - window.grafanaBootData.settings.featureToggles - ) { - serverDefault = window.grafanaBootData.settings.featureToggles.default_sidebar_docked; + function setAuto(val) { + try { + localStorage.setItem(key, val ? 'true' : 'false'); + localStorage.setItem(autoKey, '1'); + } catch (e) { + // ignore + } } - // Fallback: a page-global var you can set for quick testing in index.html - let globalDefault = typeof window !== 'undefined' ? window.__defaultSidebarDocked : undefined; - - // If neither is provided, leave the existing Grafana default (true/docked). - let val = - typeof serverDefault !== 'undefined' - ? serverDefault - : typeof globalDefault !== 'undefined' - ? globalDefault - : undefined; - - if (typeof val !== 'undefined') { - // localStorage stores strings; Grafana historically uses 'true'/'false' for this key. - localStorage.setItem(key, val ? 'true' : 'false'); + function setIfAbsent(val) { + try { + if (localStorage.getItem(key) === null) { + setAuto(val); + } + } catch (e) { + // ignore + } } + + function setIfAutoOrAbsent(val) { + try { + let cur = localStorage.getItem(key); + if (cur === null || isAutoSet()) { + setAuto(val); + } + } catch (e) { + // ignore + } + } + + function applyDefault(preferServer) { + try { + // If a real user preference exists (not marked as auto) and preferServer is true, + // we should NOT override it. setIfAutoOrAbsent will only override if auto or absent. + // preferServer indicates this call is from authoritative bootdata (true) or inline (false). + let serverDefault; + if ( + typeof window !== 'undefined' && + window.grafanaBootData && + window.grafanaBootData.settings && + window.grafanaBootData.settings.featureToggles && + typeof window.grafanaBootData.settings.featureToggles.default_sidebar_docked !== 'undefined' + ) { + serverDefault = window.grafanaBootData.settings.featureToggles.default_sidebar_docked; + } + + let globalDefault = typeof window !== 'undefined' ? window.__defaultSidebarDocked : undefined; + + let val = + typeof serverDefault !== 'undefined' + ? serverDefault + : typeof globalDefault !== 'undefined' + ? globalDefault + : undefined; + + if (typeof val === 'undefined') { + return; + } + + // If this is called for authoritative data (preferServer === true), allow override of previously + // auto-set values; if it's non-authoritative (inline), only set if absent. + if (preferServer) { + setIfAutoOrAbsent(val); + } else { + setIfAbsent(val); + } + } catch (e) { + // ignore + } + } + + // Fast-path: try immediate apply from inline bootdata (non-authoritative) + applyDefault(false); + + // If async bootdata is fetched, wait for the promise and apply authoritative value + try { + if ( + typeof window !== 'undefined' && + window.__grafana_boot_data_promise && + typeof window.__grafana_boot_data_promise.then === 'function' + ) { + window.__grafana_boot_data_promise + .then(function () { + applyDefault(true); + }) + .catch(function () { + // ignore + }); + } + } catch (e) { + // ignore + } + + // Fallback: try again shortly after load as a final chance (authoritative) + setTimeout(function () { + applyDefault(true); + }, 1500); } catch (e) { - // Keep page stable if something goes wrong + // Do not let this break the page } })();