diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index bd3ac76eec8..4aa50bc72f6 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -22,6 +22,23 @@ type PluginCss struct { Dark string `json:"dark"` } +const ( + // These weights may be used by an extension to reliably place + // itself in relation to a particular item in the menu. The weights + // are negative to ensure that the default items are placed above + // any items with default weight. + + WeightCreate = (iota - 20) * 100 + WeightDashboard + WeightExplore + WeightProfile + WeightAlerting + WeightPlugin + WeightConfig + WeightAdmin + WeightHelp +) + type NavLink struct { Id string `json:"id,omitempty"` Text string `json:"text,omitempty"` @@ -31,6 +48,7 @@ type NavLink struct { Img string `json:"img,omitempty"` Url string `json:"url,omitempty"` Target string `json:"target,omitempty"` + SortWeight int64 `json:"sortWeight,omitempty"` Divider bool `json:"divider,omitempty"` HideFromMenu bool `json:"hideFromMenu,omitempty"` HideFromTabs bool `json:"hideFromTabs,omitempty"` diff --git a/pkg/api/index.go b/pkg/api/index.go index 69ac9d60a46..678d42a6dcb 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "sort" "strings" "github.com/grafana/grafana/pkg/api/dtos" @@ -115,11 +116,12 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}) data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Create", - Id: "create", - Icon: "fa fa-fw fa-plus", - Url: setting.AppSubUrl + "/dashboard/new", - Children: children, + Text: "Create", + Id: "create", + Icon: "fa fa-fw fa-plus", + Url: setting.AppSubUrl + "/dashboard/new", + Children: children, + SortWeight: dtos.WeightCreate, }) } @@ -132,21 +134,23 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Dashboards", - Id: "dashboards", - SubTitle: "Manage dashboards & folders", - Icon: "gicon gicon-dashboard", - Url: setting.AppSubUrl + "/", - Children: dashboardChildNavs, + Text: "Dashboards", + Id: "dashboards", + SubTitle: "Manage dashboards & folders", + Icon: "gicon gicon-dashboard", + Url: setting.AppSubUrl + "/", + SortWeight: dtos.WeightDashboard, + Children: dashboardChildNavs, }) if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR || setting.ViewersCanEdit) { data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Explore", - Id: "explore", - SubTitle: "Explore your data", - Icon: "gicon gicon-explore", - Url: setting.AppSubUrl + "/explore", + Text: "Explore", + Id: "explore", + SubTitle: "Explore your data", + Icon: "gicon gicon-explore", + SortWeight: dtos.WeightExplore, + Url: setting.AppSubUrl + "/explore", }) } @@ -163,6 +167,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Img: data.User.GravatarUrl, Url: setting.AppSubUrl + "/profile", HideFromMenu: true, + SortWeight: dtos.WeightProfile, Children: []*dtos.NavLink{ {Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "gicon gicon-preferences"}, {Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", Icon: "fa fa-fw fa-lock", HideFromMenu: true}, @@ -186,12 +191,13 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Alerting", - SubTitle: "Alert rules & notifications", - Id: "alerting", - Icon: "gicon gicon-alert", - Url: setting.AppSubUrl + "/alerting/list", - Children: alertChildNavs, + Text: "Alerting", + SubTitle: "Alert rules & notifications", + Id: "alerting", + Icon: "gicon gicon-alert", + Url: setting.AppSubUrl + "/alerting/list", + Children: alertChildNavs, + SortWeight: dtos.WeightAlerting, }) } @@ -203,10 +209,11 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er for _, plugin := range enabledPlugins.Apps { if plugin.Pinned { appLink := &dtos.NavLink{ - Text: plugin.Name, - Id: "plugin-page-" + plugin.Id, - Url: plugin.DefaultNavUrl, - Img: plugin.Info.Logos.Small, + Text: plugin.Name, + Id: "plugin-page-" + plugin.Id, + Url: plugin.DefaultNavUrl, + Img: plugin.Info.Logos.Small, + SortWeight: dtos.WeightPlugin, } for _, include := range plugin.Includes { @@ -297,12 +304,13 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Id: "cfg", - Text: "Configuration", - SubTitle: "Organization: " + c.OrgName, - Icon: "gicon gicon-cog", - Url: configNodes[0].Url, - Children: configNodes, + Id: "cfg", + Text: "Configuration", + SubTitle: "Organization: " + c.OrgName, + Icon: "gicon gicon-cog", + Url: configNodes[0].Url, + SortWeight: dtos.WeightConfig, + Children: configNodes, }) if c.IsGrafanaAdmin { @@ -326,6 +334,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Id: "admin", Icon: "gicon gicon-shield", Url: setting.AppSubUrl + "/admin/users", + SortWeight: dtos.WeightAdmin, Children: adminNavLinks, }) } @@ -337,6 +346,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Url: "#", Icon: "gicon gicon-question", HideFromMenu: true, + SortWeight: dtos.WeightHelp, Children: []*dtos.NavLink{ {Text: "Keyboard shortcuts", Url: "/shortcuts", Icon: "fa fa-fw fa-keyboard-o", Target: "_self"}, {Text: "Community site", Url: "http://community.grafana.com", Icon: "fa fa-fw fa-comment", Target: "_blank"}, @@ -345,6 +355,10 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er }) hs.HooksService.RunIndexDataHooks(&data) + + sort.SliceStable(data.NavTree, func(i, j int) bool { + return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight + }) return &data, nil }