Compare commits

...

5 Commits

7 changed files with 189 additions and 0 deletions

View File

@@ -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/

View File

@@ -0,0 +1,27 @@
package api
import (
"encoding/json"
"net/http"
"github.com/grafana/grafana/pkg/setting"
)
// Note: This is a minimal example showing how to add feature toggles to the boot payload.
// Integrate with Grafana's actual frontend boot data code path (where the server renders JSON into the page).
func frontendSettingsHandler(w http.ResponseWriter, r *http.Request) {
// Example existing boot data:
boot := map[string]interface{}{
"settings": map[string]interface{}{},
}
// Add our feature toggle into the front-end settings payload.
settings := boot["settings"].(map[string]interface{})
settings["featureToggles"] = map[string]interface{}{
"default_sidebar_docked": setting.FeatureToggleConfig.DefaultSidebarDocked,
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(boot)
}

View File

@@ -220,6 +220,12 @@ 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{}
}
data.Settings.FeatureToggles["default_sidebar_docked"] = setting.FeatureToggleConfig.DefaultSidebarDocked
data.NavTree.Sort()
return &data, nil

View File

@@ -0,0 +1,21 @@
package setting
import (
"gopkg.in/ini.v1"
)
// FeatureToggles holds lightweight feature flags that can be set via grafana.ini
type FeatureToggles struct {
// DefaultSidebarDocked determines the default state of the left navigation when no per-browser
// preference exists in localStorage (grafana.navigation.docked).
DefaultSidebarDocked bool `json:"default_sidebar_docked"`
}
var FeatureToggleConfig = &FeatureToggles{DefaultSidebarDocked: true}
// loadFeatureToggles reads [feature_toggles] section from the provided INI file.
// This is a minimal helper; integrate it into your existing settings loader.
func loadFeatureToggles(cfg *ini.File) {
sec := cfg.Section("feature_toggles")
FeatureToggleConfig.DefaultSidebarDocked = sec.Key("default_sidebar_docked").MustBool(true)
}

View File

@@ -980,6 +980,10 @@ func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) {
defaultConfigFile := path.Join(cfg.HomePath, "conf/defaults.ini")
cfg.configFiles = append(cfg.configFiles, defaultConfigFile)
// M@ DEBUG do not commit
cfg.Logger.Info("In loadConfiguration M@-----------")
fmt.Printf("In loadConfiguration M@-----------\n")
// check if config file exists
if _, err := os.Stat(defaultConfigFile); os.IsNotExist(err) {
fmt.Println("Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath")
@@ -1019,6 +1023,8 @@ func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) {
// apply command line overrides
cfg.applyCommandLineProperties(commandLineProps, parsedFile)
loadFeatureToggles(parsedFile)
// evaluate config values containing environment variables
err = expandConfig(parsedFile)
if err != nil {

View File

@@ -0,0 +1,121 @@
/* 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';
function isAutoSet() {
try {
return localStorage.getItem(autoKey) === '1';
} catch (e) {
return false;
}
}
function setAuto(val) {
try {
localStorage.setItem(key, val ? 'true' : 'false');
localStorage.setItem(autoKey, '1');
} catch (e) {
// ignore
}
}
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) {
// Do not let this break the page
}
})();

View File

@@ -267,6 +267,8 @@
<div id="reactRoot"></div>
<script src="/public/scripts/set-default-sidebar.js"></script>
<script nonce="[[.Nonce]]">
window.grafanaBootData = {
user: [[.User]],