From 138575cbe9ae3c2fe6ef62bf5edc472c79d52040 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 31 Jan 2023 19:14:15 +0000 Subject: [PATCH] Config: Refactor frontend settings to struct (#61990) * Config: Make frontend settings a struct rather than map remove frontend settings to setting package remove frontend settings struct to dtos package rearrange structs to avoid cycles rename getFrontendSettings fn omitempty fix login test fix middleware test * wip some enterprise types * cleanup, moved structs from enterprise * ci --- pkg/api/dtos/frontend_settings.go | 227 +++++++++++++++++++++++++++ pkg/api/dtos/index.go | 2 +- pkg/api/frontendsettings.go | 252 ++++++++++++++++-------------- pkg/api/index.go | 10 +- pkg/api/login.go | 2 +- pkg/api/login_test.go | 2 +- pkg/middleware/middleware_test.go | 2 +- pkg/services/thumbs/dummy.go | 7 +- pkg/services/thumbs/models.go | 10 -- pkg/services/thumbs/service.go | 19 +-- 10 files changed, 383 insertions(+), 150 deletions(-) create mode 100644 pkg/api/dtos/frontend_settings.go diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go new file mode 100644 index 00000000000..778ab9683d3 --- /dev/null +++ b/pkg/api/dtos/frontend_settings.go @@ -0,0 +1,227 @@ +package dtos + +import ( + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/setting" +) + +type FrontendSettingsAuthDTO struct { + OAuthSkipOrgRoleUpdateSync bool `json:"OAuthSkipOrgRoleUpdateSync"` + SAMLSkipOrgRoleSync bool `json:"SAMLSkipOrgRoleSync"` + LDAPSkipOrgRoleSync bool `json:"LDAPSkipOrgRoleSync"` + GoogleSkipOrgRoleSync bool `json:"GoogleSkipOrgRoleSync"` + JWTAuthSkipOrgRoleSync bool `json:"JWTAuthSkipOrgRoleSync"` + GrafanaComSkipOrgRoleSync bool `json:"GrafanaComSkipOrgRoleSync"` + AzureADSkipOrgRoleSync bool `json:"AzureADSkipOrgRoleSync"` + GithubSkipOrgRoleSync bool `json:"GithubSkipOrgRoleSync"` + GitLabSkipOrgRoleSync bool `json:"GitLabSkipOrgRoleSync"` + OktaSkipOrgRoleSync bool `json:"OktaSkipOrgRoleSync"` + DisableSyncLock bool `json:"DisableSyncLock"` +} + +type FrontendSettingsBuildInfoDTO struct { + HideVersion bool `json:"hideVersion"` + Version string `json:"version"` + Commit string `json:"commit"` + Buildstamp int64 `json:"buildstamp"` + Edition string `json:"edition"` + LatestVersion string `json:"latestVersion"` + HasUpdate bool `json:"hasUpdate"` + Env string `json:"env"` +} + +type FrontendSettingsLicenseInfoDTO struct { + Expiry int64 `json:"expiry"` + StateInfo string `json:"stateInfo"` + LicenseUrl string `json:"licenseUrl"` + Edition string `json:"edition"` + EnabledFeatures map[string]bool `json:"enabledFeatures"` + + // Enterprise-only + TrialExpiry *int64 `json:"trialExpiry,omitempty"` + AppUrl *string `json:"appUrl,omitempty"` +} + +type FrontendSettingsAzureDTO struct { + Cloud string `json:"cloud"` + ManagedIdentityEnabled bool `json:"managedIdentityEnabled"` +} + +type FrontendSettingsCachingDTO struct { + Enabled bool `json:"enabled"` +} + +type FrontendSettingsRecordedQueriesDTO struct { + Enabled bool `json:"enabled"` +} + +type FrontendSettingsReportingDTO struct { + Enabled bool `json:"enabled"` +} + +type FrontendSettingsUnifiedAlertingDTO struct { + MinInterval string `json:"minInterval"` +} + +type DashboardPreviewsSystemRequirements struct { + Met bool `json:"met"` + RequiredImageRendererPluginVersion string `json:"requiredImageRendererPluginVersion"` +} + +type DashboardPreviewsSetupConfig struct { + SystemRequirements DashboardPreviewsSystemRequirements `json:"systemRequirements"` + ThumbnailsExist bool `json:"thumbnailsExist"` +} + +// Enterprise-only +type FrontendSettingsLicensingDTO struct { + Slug *string `json:"slug,omitempty"` + LimitBy *string `json:"limitBy,omitempty"` + IncludedUsers *int64 `json:"includedUsers,omitempty"` + LicenseExpiry *int64 `json:"licenseExpiry,omitempty"` + LicenseExpiryWarnDays *int64 `json:"licenseExpiryWarnDays,omitempty"` + TokenExpiry *int64 `json:"tokenExpiry,omitempty"` + IsTrial *bool `json:"isTrial,omitempty"` + TokenExpiryWarnDays *int64 `json:"tokenExpiryWarnDays,omitempty"` + UsageBilling *bool `json:"usageBilling,omitempty"` + ActiveAdminsAndEditors *int64 `json:"activeAdminsAndEditors,omitempty"` + ActiveViewers *int64 `json:"activeViewers,omitempty"` + ActiveUsers *int64 `json:"ActiveUsers,omitempty"` +} + +// Enterprise-only +type FrontendSettingsFooterConfigItemDTO struct { + Text string `json:"text"` + Url string `json:"url"` + Icon string `json:"icon"` + Target string `json:"blank"` +} + +// Enterprise-only +type FrontendSettingsPublicDashboardFooterConfigDTO struct { + Hide bool `json:"hide"` + Text string `json:"text"` + Logo string `json:"logo"` + Link string `json:"link"` +} + +// Enterprise-only +type FrontendSettingsWhitelabelingDTO struct { + Links []FrontendSettingsFooterConfigItemDTO `json:"links"` + LoginTitle string `json:"loginTitle"` + + AppTitle *string `json:"appTitle,omitempty"` + LoginLogo *string `json:"loginLogo,omitempty"` + MenuLogo *string `json:"menuLogo,omitempty"` + LoginBackground *string `json:"loginBackground,omitempty"` + LoginSubtitle *string `json:"loginSubtitle,omitempty"` + LoginBoxBackground *string `json:"loginBoxBackground,omitempty"` + LoadingLogo *string `json:"loadingLogo,omitempty"` + PublicDashboardFooter *FrontendSettingsPublicDashboardFooterConfigDTO `json:"publicDashboardFooter,omitempty"` // PR TODO: type this properly +} + +type FrontendSettingsDTO struct { + DefaultDatasource string `json:"defaultDatasource"` + Datasources map[string]plugins.DataSourceDTO `json:"datasources"` + MinRefreshInterval string `json:"minRefreshInterval"` + Panels map[string]plugins.PanelDTO `json:"panels"` + AppUrl string `json:"appUrl"` + AppSubUrl string `json:"appSubUrl"` + AllowOrgCreate bool `json:"allowOrgCreate"` + AuthProxyEnabled bool `json:"authProxyEnabled"` + LdapEnabled bool `json:"ldapEnabled"` + JwtHeaderName string `json:"jwtHeaderName"` + JwtUrlLogin bool `json:"jwtUrlLogin"` + AlertingEnabled bool `json:"alertingEnabled"` + AlertingErrorOrTimeout string `json:"alertingErrorOrTimeout"` + AlertingNoDataOrNullValues string `json:"alertingNoDataOrNullValues"` + AlertingMinInterval int64 `json:"alertingMinInterval"` + LiveEnabled bool `json:"liveEnabled"` + AutoAssignOrg bool `json:"autoAssignOrg"` + + VerifyEmailEnabled bool `json:"verifyEmailEnabled"` + SigV4AuthEnabled bool `json:"sigV4AuthEnabled"` + AzureAuthEnabled bool `json:"azureAuthEnabled"` + RbacEnabled bool `json:"rbacEnabled"` + ExploreEnabled bool `json:"exploreEnabled"` + HelpEnabled bool `json:"helpEnabled"` + ProfileEnabled bool `json:"profileEnabled"` + QueryHistoryEnabled bool `json:"queryHistoryEnabled"` + + GoogleAnalyticsId string `json:"googleAnalyticsId"` + GoogleAnalytics4Id string `json:"googleAnalytics4Id"` + GoogleAnalytics4SendManualPageViews bool `json:"GoogleAnalytics4SendManualPageViews"` + + RudderstackWriteKey string `json:"rudderstackWriteKey"` + RudderstackDataPlaneUrl string `json:"rudderstackDataPlaneUrl"` + RudderstackSdkUrl string `json:"rudderstackSdkUrl"` + RudderstackConfigUrl string `json:"rudderstackConfigUrl"` + + FeedbackLinksEnabled bool `json:"feedbackLinksEnabled"` + ApplicationInsightsConnectionString string `json:"applicationInsightsConnectionString"` + ApplicationInsightsEndpointUrl string `json:"applicationInsightsEndpointUrl"` + DisableLoginForm bool `json:"disableLoginForm"` + DisableUserSignUp bool `json:"disableUserSignUp"` + LoginHint string `json:"loginHint"` + PasswordHint string `json:"passwordHint"` + ExternalUserMngInfo string `json:"externalUserMngInfo"` + ExternalUserMngLinkUrl string `json:"externalUserMngLinkUrl"` + ExternalUserMngLinkName string `json:"externalUserMngLinkName"` + ViewersCanEdit bool `json:"viewersCanEdit"` + AngularSupportEnabled bool `json:"angularSupportEnabled"` + EditorsCanAdmin bool `json:"editorsCanAdmin"` + DisableSanitizeHtml bool `json:"disableSanitizeHtml"` + PluginsToPreload []*plugins.PreloadPlugin `json:"pluginsToPreload"` + + Auth FrontendSettingsAuthDTO `json:"auth"` + + BuildInfo FrontendSettingsBuildInfoDTO `json:"buildInfo"` + + LicenseInfo FrontendSettingsLicenseInfoDTO `json:"licenseInfo"` + + FeatureToggles map[string]bool `json:"featureToggles"` + RendererAvailable bool `json:"rendererAvailable"` + RendererVersion string `json:"rendererVersion"` + SecretsManagerPluginEnabled bool `json:"secretsManagerPluginEnabled"` + Http2Enabled bool `json:"http2Enabled"` + Sentry setting.Sentry `json:"sentry"` + GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"` + PluginCatalogURL string `json:"pluginCatalogURL"` + PluginAdminEnabled bool `json:"pluginAdminEnabled"` + PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"` + PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"` + ExpressionsEnabled bool `json:"expressionsEnabled"` + AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"` + AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"` + SupportBundlesEnabled bool `json:"supportBundlesEnabled"` + SnapshotEnabled bool `json:"snapshotEnabled"` + + Azure FrontendSettingsAzureDTO `json:"azure"` + + Caching FrontendSettingsCachingDTO `json:"caching"` + RecordedQueries FrontendSettingsRecordedQueriesDTO `json:"recordedQueries"` + Reporting FrontendSettingsReportingDTO `json:"reporting"` + UnifiedAlertingEnabled bool `json:"unifiedAlertingEnabled"` + UnifiedAlerting FrontendSettingsUnifiedAlertingDTO `json:"unifiedAlerting"` + Oauth map[string]interface{} `json:"oauth"` + SamlEnabled bool `json:"samlEnabled"` + SamlName string `json:"samlName"` + TokenExpirationDayLimit int `json:"tokenExpirationDayLimit"` + + DashboardPreviews DashboardPreviewsSetupConfig `json:"dashboardPreviews,omitempty"` + + GeomapDefaultBaseLayerConfig *map[string]interface{} `json:"geomapDefaultBaseLayerConfig,omitempty"` + GeomapDisableCustomBaseLayer bool `json:"geomapDisableCustomBaseLayer"` + + IsPublicDashboardView bool `json:"isPublicDashboardView"` + + DateFormats setting.DateFormats `json:"dateFormats,omitempty"` + + LoginError string `json:"loginError,omitempty"` + + PluginsCDNBaseURL string `json:"pluginsCDNBaseURL,omitempty"` + + // Enterprise + Licensing *FrontendSettingsLicensingDTO `json:"licensing,omitempty"` + Whitelabeling *FrontendSettingsWhitelabelingDTO `json:"whitelabeling,omitempty"` +} diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 049a13f0425..8e5face1a97 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -9,7 +9,7 @@ import ( type IndexViewData struct { User *CurrentUser - Settings map[string]interface{} + Settings *FrontendSettingsDTO AppUrl string AppSubUrl string GoogleAnalyticsId string diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 130b8d34a91..ebdb2c50308 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -20,7 +21,7 @@ import ( ) func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) { - settings, err := hs.getFrontendSettingsMap(c) + settings, err := hs.getFrontendSettings(c) if err != nil { c.JsonApiErr(400, "Failed to get frontend settings", err) return @@ -29,8 +30,8 @@ func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) { c.JSON(http.StatusOK, settings) } -// getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation. -func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[string]interface{}, error) { +// getFrontendSettings returns a json object with all the settings needed for front end initialisation. +func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.FrontendSettingsDTO, error) { enabledPlugins, err := hs.enabledPlugins(c.Req.Context(), c.OrgID) if err != nil { return nil, err @@ -97,121 +98,137 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[st hasAccess := accesscontrol.HasAccess(hs.AccessControl, c) secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) == nil - jsonObj := map[string]interface{}{ - "defaultDatasource": defaultDS, - "datasources": dataSources, - "minRefreshInterval": setting.MinRefreshInterval, - "panels": panels, - "appUrl": hs.Cfg.AppURL, - "appSubUrl": hs.Cfg.AppSubURL, - "allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin, - "authProxyEnabled": setting.AuthProxyEnabled, - "ldapEnabled": hs.Cfg.LDAPEnabled, - "jwtHeaderName": hs.Cfg.JWTAuthHeaderName, - "jwtUrlLogin": hs.Cfg.JWTAuthURLLogin, - "alertingEnabled": setting.AlertingEnabled, - "alertingErrorOrTimeout": setting.AlertingErrorOrTimeout, - "alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues, - "alertingMinInterval": setting.AlertingMinInterval, - "liveEnabled": hs.Cfg.LiveMaxConnections != 0, - "autoAssignOrg": setting.AutoAssignOrg, - "verifyEmailEnabled": setting.VerifyEmailEnabled, - "sigV4AuthEnabled": setting.SigV4AuthEnabled, - "azureAuthEnabled": setting.AzureAuthEnabled, - "rbacEnabled": hs.Cfg.RBACEnabled, - "exploreEnabled": setting.ExploreEnabled, - "helpEnabled": setting.HelpEnabled, - "profileEnabled": setting.ProfileEnabled, - "queryHistoryEnabled": hs.Cfg.QueryHistoryEnabled, - "googleAnalyticsId": setting.GoogleAnalyticsId, - "googleAnalytics4Id": setting.GoogleAnalytics4Id, - "GoogleAnalytics4SendManualPageViews": setting.GoogleAnalytics4SendManualPageViews, - "rudderstackWriteKey": setting.RudderstackWriteKey, - "rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl, - "rudderstackSdkUrl": setting.RudderstackSdkUrl, - "rudderstackConfigUrl": setting.RudderstackConfigUrl, - "feedbackLinksEnabled": hs.Cfg.FeedbackLinksEnabled, - "applicationInsightsConnectionString": hs.Cfg.ApplicationInsightsConnectionString, - "applicationInsightsEndpointUrl": hs.Cfg.ApplicationInsightsEndpointUrl, - "disableLoginForm": setting.DisableLoginForm, - "disableUserSignUp": !setting.AllowUserSignUp, - "loginHint": setting.LoginHint, - "passwordHint": setting.PasswordHint, - "externalUserMngInfo": setting.ExternalUserMngInfo, - "externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl, - "externalUserMngLinkName": setting.ExternalUserMngLinkName, - "viewersCanEdit": setting.ViewersCanEdit, - "angularSupportEnabled": hs.Cfg.AngularSupportEnabled, - "editorsCanAdmin": hs.Cfg.EditorsCanAdmin, - "disableSanitizeHtml": hs.Cfg.DisableSanitizeHtml, - "pluginsToPreload": pluginsToPreload, - "auth": map[string]interface{}{ - "OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync, - "SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false), - "LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync, - "GithubSkipOrgRoleSync": hs.Cfg.GithubSkipOrgRoleSync, - "GoogleSkipOrgRoleSync": hs.Cfg.GoogleSkipOrgRoleSync, - "JWTAuthSkipOrgRoleSync": hs.Cfg.JWTAuthSkipOrgRoleSync, - "GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync, - "GitLabSkipOrgRoleSync": hs.Cfg.GitLabSkipOrgRoleSync, - "AzureADSkipOrgRoleSync": hs.Cfg.AzureADSkipOrgRoleSync, - "OktaSkipOrgRoleSync": hs.Cfg.OktaSkipOrgRoleSync, - "DisableSyncLock": hs.Cfg.DisableSyncLock, + frontendSettings := &dtos.FrontendSettingsDTO{ + DefaultDatasource: defaultDS, + Datasources: dataSources, + MinRefreshInterval: setting.MinRefreshInterval, + Panels: panels, + AppUrl: hs.Cfg.AppURL, + AppSubUrl: hs.Cfg.AppSubURL, + AllowOrgCreate: (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin, + AuthProxyEnabled: setting.AuthProxyEnabled, + LdapEnabled: hs.Cfg.LDAPEnabled, + JwtHeaderName: hs.Cfg.JWTAuthHeaderName, + JwtUrlLogin: hs.Cfg.JWTAuthURLLogin, + AlertingErrorOrTimeout: setting.AlertingErrorOrTimeout, + AlertingNoDataOrNullValues: setting.AlertingNoDataOrNullValues, + AlertingMinInterval: setting.AlertingMinInterval, + LiveEnabled: hs.Cfg.LiveMaxConnections != 0, + AutoAssignOrg: setting.AutoAssignOrg, + VerifyEmailEnabled: setting.VerifyEmailEnabled, + SigV4AuthEnabled: setting.SigV4AuthEnabled, + AzureAuthEnabled: setting.AzureAuthEnabled, + RbacEnabled: hs.Cfg.RBACEnabled, + ExploreEnabled: setting.ExploreEnabled, + HelpEnabled: setting.HelpEnabled, + ProfileEnabled: setting.ProfileEnabled, + QueryHistoryEnabled: hs.Cfg.QueryHistoryEnabled, + GoogleAnalyticsId: setting.GoogleAnalyticsId, + GoogleAnalytics4Id: setting.GoogleAnalytics4Id, + GoogleAnalytics4SendManualPageViews: setting.GoogleAnalytics4SendManualPageViews, + RudderstackWriteKey: setting.RudderstackWriteKey, + RudderstackDataPlaneUrl: setting.RudderstackDataPlaneUrl, + RudderstackSdkUrl: setting.RudderstackSdkUrl, + RudderstackConfigUrl: setting.RudderstackConfigUrl, + FeedbackLinksEnabled: hs.Cfg.FeedbackLinksEnabled, + ApplicationInsightsConnectionString: hs.Cfg.ApplicationInsightsConnectionString, + ApplicationInsightsEndpointUrl: hs.Cfg.ApplicationInsightsEndpointUrl, + DisableLoginForm: setting.DisableLoginForm, + DisableUserSignUp: !setting.AllowUserSignUp, + LoginHint: setting.LoginHint, + PasswordHint: setting.PasswordHint, + ExternalUserMngInfo: setting.ExternalUserMngInfo, + ExternalUserMngLinkUrl: setting.ExternalUserMngLinkUrl, + ExternalUserMngLinkName: setting.ExternalUserMngLinkName, + ViewersCanEdit: setting.ViewersCanEdit, + AngularSupportEnabled: hs.Cfg.AngularSupportEnabled, + EditorsCanAdmin: hs.Cfg.EditorsCanAdmin, + DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml, + PluginsToPreload: pluginsToPreload, + DateFormats: hs.Cfg.DateFormats, + + Auth: dtos.FrontendSettingsAuthDTO{ + OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync, + SAMLSkipOrgRoleSync: hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false), + LDAPSkipOrgRoleSync: hs.Cfg.LDAPSkipOrgRoleSync, + GoogleSkipOrgRoleSync: hs.Cfg.GoogleSkipOrgRoleSync, + JWTAuthSkipOrgRoleSync: hs.Cfg.JWTAuthSkipOrgRoleSync, + GrafanaComSkipOrgRoleSync: hs.Cfg.GrafanaComSkipOrgRoleSync, + AzureADSkipOrgRoleSync: hs.Cfg.AzureADSkipOrgRoleSync, + GithubSkipOrgRoleSync: hs.Cfg.GithubSkipOrgRoleSync, + GitLabSkipOrgRoleSync: hs.Cfg.GitLabSkipOrgRoleSync, + OktaSkipOrgRoleSync: hs.Cfg.OktaSkipOrgRoleSync, + DisableSyncLock: hs.Cfg.DisableSyncLock, }, - "buildInfo": map[string]interface{}{ - "hideVersion": hideVersion, - "version": version, - "commit": commit, - "buildstamp": buildstamp, - "edition": hs.License.Edition(), - "latestVersion": hs.grafanaUpdateChecker.LatestVersion(), - "hasUpdate": hs.grafanaUpdateChecker.UpdateAvailable(), - "env": setting.Env, + + BuildInfo: dtos.FrontendSettingsBuildInfoDTO{ + HideVersion: hideVersion, + Version: version, + Commit: commit, + Buildstamp: buildstamp, + Edition: hs.License.Edition(), + LatestVersion: hs.grafanaUpdateChecker.LatestVersion(), + HasUpdate: hs.grafanaUpdateChecker.UpdateAvailable(), + Env: setting.Env, }, - "licenseInfo": map[string]interface{}{ - "expiry": hs.License.Expiry(), - "stateInfo": hs.License.StateInfo(), - "licenseUrl": hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, licensing.PageAccess)), - "edition": hs.License.Edition(), - "enabledFeatures": hs.License.EnabledFeatures(), + + LicenseInfo: dtos.FrontendSettingsLicenseInfoDTO{ + Expiry: hs.License.Expiry(), + StateInfo: hs.License.StateInfo(), + LicenseUrl: hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, licensing.PageAccess)), + Edition: hs.License.Edition(), + EnabledFeatures: hs.License.EnabledFeatures(), }, - "featureToggles": hs.Features.GetEnabled(c.Req.Context()), - "rendererAvailable": hs.RenderService.IsAvailable(c.Req.Context()), - "rendererVersion": hs.RenderService.Version(), - "secretsManagerPluginEnabled": secretsManagerPluginEnabled, - "http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme, - "sentry": hs.Cfg.Sentry, - "grafanaJavascriptAgent": hs.Cfg.GrafanaJavascriptAgent, - "pluginCatalogURL": hs.Cfg.PluginCatalogURL, - "pluginAdminEnabled": hs.Cfg.PluginAdminEnabled, - "pluginAdminExternalManageEnabled": hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled, - "pluginCatalogHiddenPlugins": hs.Cfg.PluginCatalogHiddenPlugins, - "expressionsEnabled": hs.Cfg.ExpressionsEnabled, - "awsAllowedAuthProviders": hs.Cfg.AWSAllowedAuthProviders, - "awsAssumeRoleEnabled": hs.Cfg.AWSAssumeRoleEnabled, - "supportBundlesEnabled": isSupportBundlesEnabled(hs), - "azure": map[string]interface{}{ - "cloud": hs.Cfg.Azure.Cloud, - "managedIdentityEnabled": hs.Cfg.Azure.ManagedIdentityEnabled, + + FeatureToggles: hs.Features.GetEnabled(c.Req.Context()), + RendererAvailable: hs.RenderService.IsAvailable(c.Req.Context()), + RendererVersion: hs.RenderService.Version(), + SecretsManagerPluginEnabled: secretsManagerPluginEnabled, + Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme, + Sentry: hs.Cfg.Sentry, + GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent, + PluginCatalogURL: hs.Cfg.PluginCatalogURL, + PluginAdminEnabled: hs.Cfg.PluginAdminEnabled, + PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled, + PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins, + ExpressionsEnabled: hs.Cfg.ExpressionsEnabled, + AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders, + AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled, + SupportBundlesEnabled: isSupportBundlesEnabled(hs), + + Azure: dtos.FrontendSettingsAzureDTO{ + Cloud: hs.Cfg.Azure.Cloud, + ManagedIdentityEnabled: hs.Cfg.Azure.ManagedIdentityEnabled, }, - "caching": map[string]bool{ - "enabled": hs.Cfg.SectionWithEnvOverrides("caching").Key("enabled").MustBool(true), + + Caching: dtos.FrontendSettingsCachingDTO{ + Enabled: hs.Cfg.SectionWithEnvOverrides("caching").Key("enabled").MustBool(true), }, - "recordedQueries": map[string]bool{ - "enabled": hs.Cfg.SectionWithEnvOverrides("recorded_queries").Key("enabled").MustBool(true), + RecordedQueries: dtos.FrontendSettingsRecordedQueriesDTO{ + Enabled: hs.Cfg.SectionWithEnvOverrides("recorded_queries").Key("enabled").MustBool(true), }, - "reporting": map[string]bool{ - "enabled": hs.Cfg.SectionWithEnvOverrides("reporting").Key("enabled").MustBool(true), + Reporting: dtos.FrontendSettingsReportingDTO{ + Enabled: hs.Cfg.SectionWithEnvOverrides("reporting").Key("enabled").MustBool(true), }, - "unifiedAlertingEnabled": hs.Cfg.UnifiedAlerting.Enabled, - "unifiedAlerting": map[string]interface{}{ - "minInterval": hs.Cfg.UnifiedAlerting.MinInterval.String(), + + UnifiedAlerting: dtos.FrontendSettingsUnifiedAlertingDTO{ + MinInterval: hs.Cfg.UnifiedAlerting.MinInterval.String(), }, - "oauth": hs.getEnabledOAuthProviders(), - "samlEnabled": hs.samlEnabled(), - "samlName": hs.samlName(), - "tokenExpirationDayLimit": hs.Cfg.SATokenExpirationDayLimit, - "snapshotEnabled": hs.Cfg.SnapshotEnabled, + + Oauth: hs.getEnabledOAuthProviders(), + SamlEnabled: hs.samlEnabled(), + SamlName: hs.samlName(), + TokenExpirationDayLimit: hs.Cfg.SATokenExpirationDayLimit, + + SnapshotEnabled: hs.Cfg.SnapshotEnabled, + } + + if hs.Cfg.UnifiedAlerting.Enabled != nil { + frontendSettings.UnifiedAlertingEnabled = *hs.Cfg.UnifiedAlerting.Enabled + } + + if setting.AlertingEnabled != nil { + frontendSettings.AlertingEnabled = *setting.AlertingEnabled } if hs.pluginsCDNService != nil && hs.pluginsCDNService.IsEnabled() { @@ -219,21 +236,22 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[st if err != nil { return nil, fmt.Errorf("plugins cdn base url: %w", err) } - jsonObj["pluginsCDNBaseURL"] = cdnBaseURL + frontendSettings.PluginsCDNBaseURL = cdnBaseURL } if hs.ThumbService != nil { - jsonObj["dashboardPreviews"] = hs.ThumbService.GetDashboardPreviewsSetupSettings(c) + frontendSettings.DashboardPreviews = hs.ThumbService.GetDashboardPreviewsSetupSettings(c) } if hs.Cfg.GeomapDefaultBaseLayerConfig != nil { - jsonObj["geomapDefaultBaseLayerConfig"] = hs.Cfg.GeomapDefaultBaseLayerConfig - } - if !hs.Cfg.GeomapEnableCustomBaseLayers { - jsonObj["geomapDisableCustomBaseLayer"] = true + frontendSettings.GeomapDefaultBaseLayerConfig = &hs.Cfg.GeomapDefaultBaseLayerConfig } - return jsonObj, nil + if !hs.Cfg.GeomapEnableCustomBaseLayers { + frontendSettings.GeomapDisableCustomBaseLayer = true + } + + return frontendSettings, nil } func isSupportBundlesEnabled(hs *HTTPServer) bool { diff --git a/pkg/api/index.go b/pkg/api/index.go index 2aee274b792..7395929194e 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -35,12 +35,12 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV hasAccess := ac.HasAccess(hs.AccessControl, c) hasEditPerm := hasAccess(hs.editorInAnyFolder, ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionFoldersCreate))) - settings, err := hs.getFrontendSettingsMap(c) + settings, err := hs.getFrontendSettings(c) if err != nil { return nil, err } - settings["dateFormats"] = hs.Cfg.DateFormats + settings.IsPublicDashboardView = c.IsPublicDashboardView prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.UserID, OrgID: c.OrgID, Teams: c.Teams} prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) @@ -70,7 +70,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV if c.IsRenderCall && !hs.Cfg.ServeFromSubPath { appURL = fmt.Sprintf("%s://localhost:%s", hs.Cfg.Protocol, hs.Cfg.HTTPPort) appSubURL = "" - settings["appSubUrl"] = "" + settings.AppSubUrl = "" } navTree, err := hs.navTreeService.GetNavTree(c, hasEditPerm, prefs) @@ -78,10 +78,6 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV return nil, err } - if c.IsPublicDashboardView { - settings["isPublicDashboardView"] = true - } - weekStart := "" if prefs.WeekStart != nil { weekStart = *prefs.WeekStart diff --git a/pkg/api/login.go b/pkg/api/login.go index 8d041941ff4..d4cf320bc16 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -100,7 +100,7 @@ func (hs *HTTPServer) LoginView(c *contextmodel.ReqContext) { // and the view should return immediately before attempting // to login again via OAuth and enter to a redirect loop cookies.DeleteCookie(c.Resp, loginErrorCookieName, hs.CookieOptionsFromCfg) - viewData.Settings["loginError"] = loginError + viewData.Settings.LoginError = loginError c.HTML(http.StatusOK, getViewIndex(), viewData) return } diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index 402d9d77b7e..95c1b8df329 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -44,7 +44,7 @@ func fakeSetIndexViewData(t *testing.T) { setIndexViewData = func(*HTTPServer, *contextmodel.ReqContext) (*dtos.IndexViewData, error) { data := &dtos.IndexViewData{ User: &dtos.CurrentUser{}, - Settings: map[string]interface{}{}, + Settings: &dtos.FrontendSettingsDTO{}, NavTree: &navtree.NavTreeRoot{}, } return data, nil diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index ac6d530749d..eaabf0085d1 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -167,7 +167,7 @@ func TestMiddlewareContext(t *testing.T) { t.Log("Handler called") data := &dtos.IndexViewData{ User: &dtos.CurrentUser{}, - Settings: map[string]interface{}{}, + Settings: &dtos.FrontendSettingsDTO{}, NavTree: &navtree.NavTreeRoot{}, } t.Log("Calling HTML", "data", data) diff --git a/pkg/services/thumbs/dummy.go b/pkg/services/thumbs/dummy.go index 9f77fe37891..6afeea7e869 100644 --- a/pkg/services/thumbs/dummy.go +++ b/pkg/services/thumbs/dummy.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" ) @@ -31,9 +32,9 @@ func (ds *dummyService) Enabled() bool { return false } -func (ds *dummyService) GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dashboardPreviewsSetupConfig { - return dashboardPreviewsSetupConfig{ - SystemRequirements: dashboardPreviewsSystemRequirements{ +func (ds *dummyService) GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dtos.DashboardPreviewsSetupConfig { + return dtos.DashboardPreviewsSetupConfig{ + SystemRequirements: dtos.DashboardPreviewsSystemRequirements{ Met: false, RequiredImageRendererPluginVersion: "", }, diff --git a/pkg/services/thumbs/models.go b/pkg/services/thumbs/models.go index dfc6fc7d887..ed40b90f33a 100644 --- a/pkg/services/thumbs/models.go +++ b/pkg/services/thumbs/models.go @@ -55,16 +55,6 @@ type crawlStatus struct { Last time.Time `json:"last,omitempty"` } -type dashboardPreviewsSystemRequirements struct { - Met bool `json:"met"` - RequiredImageRendererPluginVersion string `json:"requiredImageRendererPluginVersion"` -} - -type dashboardPreviewsSetupConfig struct { - SystemRequirements dashboardPreviewsSystemRequirements `json:"systemRequirements"` - ThumbnailsExist bool `json:"thumbnailsExist"` -} - type ThumbnailKind string type ThumbnailState string diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index f34eb8af566..aa15717b207 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -10,6 +10,7 @@ import ( "github.com/segmentio/encoding/json" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -34,7 +35,7 @@ type Service interface { Run(ctx context.Context) error Enabled() bool GetImage(c *contextmodel.ReqContext) - GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dashboardPreviewsSetupConfig + GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dtos.DashboardPreviewsSetupConfig // from dashboard page SetImage(c *contextmodel.ReqContext) // form post @@ -319,44 +320,44 @@ func (hs *thumbService) hasAccessToPreview(c *contextmodel.ReqContext, res *Dash return true } -func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dashboardPreviewsSetupConfig { +func (hs *thumbService) GetDashboardPreviewsSetupSettings(c *contextmodel.ReqContext) dtos.DashboardPreviewsSetupConfig { return hs.getDashboardPreviewsSetupSettings(c.Req.Context()) } -func (hs *thumbService) getDashboardPreviewsSetupSettings(ctx context.Context) dashboardPreviewsSetupConfig { +func (hs *thumbService) getDashboardPreviewsSetupSettings(ctx context.Context) dtos.DashboardPreviewsSetupConfig { systemRequirements := hs.getSystemRequirements(ctx) thumbnailsExist, err := hs.thumbnailRepo.doThumbnailsExist(ctx) if err != nil { - return dashboardPreviewsSetupConfig{ + return dtos.DashboardPreviewsSetupConfig{ SystemRequirements: systemRequirements, ThumbnailsExist: false, } } - return dashboardPreviewsSetupConfig{ + return dtos.DashboardPreviewsSetupConfig{ SystemRequirements: systemRequirements, ThumbnailsExist: thumbnailsExist, } } -func (hs *thumbService) getSystemRequirements(ctx context.Context) dashboardPreviewsSystemRequirements { +func (hs *thumbService) getSystemRequirements(ctx context.Context) dtos.DashboardPreviewsSystemRequirements { res, err := hs.renderingService.HasCapability(ctx, rendering.ScalingDownImages) if err != nil { hs.log.Error("Error when verifying dashboard previews system requirements thumbnail", "err", err.Error()) - return dashboardPreviewsSystemRequirements{ + return dtos.DashboardPreviewsSystemRequirements{ Met: false, } } if !res.IsSupported { - return dashboardPreviewsSystemRequirements{ + return dtos.DashboardPreviewsSystemRequirements{ Met: false, RequiredImageRendererPluginVersion: res.SemverConstraint, } } - return dashboardPreviewsSystemRequirements{ + return dtos.DashboardPreviewsSystemRequirements{ Met: true, } }