The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/api/api.go

497 lines
29 KiB

// Package api contains API logic.
package api
import (
"time"
"github.com/go-macaron/binding"
"github.com/grafana/grafana/pkg/api/avatar"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/frontendlogging"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/middleware"
Auth: Allow expiration of API keys (#17678) * Modify backend to allow expiration of API Keys * Add middleware test for expired api keys * Modify frontend to enable expiration of API Keys * Fix frontend tests * Fix migration and add index for `expires` field * Add api key tests for database access * Substitude time.Now() by a mock for test usage * Front-end modifications * Change input label to `Time to live` * Change input behavior to comply with the other similar * Add tooltip * Modify AddApiKey api call response Expiration should be *time.Time instead of string * Present expiration date in the selected timezone * Use kbn for transforming intervals to seconds * Use `assert` library for tests * Frontend fixes Add checks for empty/undefined/null values * Change expires column from datetime to integer * Restrict api key duration input It should be interval not number * AddApiKey must complain if SecondsToLive is negative * Declare ErrInvalidApiKeyExpiration * Move configuration to auth section * Update docs * Eliminate alias for models in modified files * Omit expiration from api response if empty * Eliminate Goconvey from test file * Fix test Do not sleep, use mocked timeNow() instead * Remove index for expires from api_key table The index should be anyway on both org_id and expires fields. However this commit eliminates completely the index for now since not many rows are expected to be in this table. * Use getTimeZone function * Minor change in api key listing The frontend should display a message instead of empty string if the key does not expire.
6 years ago
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
)
var plog = log.New("api")
// registerRoutes registers all API HTTP routes.
func (hs *HTTPServer) registerRoutes() {
reqNoAuth := middleware.NoAuth()
reqSignedIn := middleware.ReqSignedIn
reqNotSignedIn := middleware.ReqNotSignedIn
reqSignedInNoAnonymous := middleware.ReqSignedInNoAnonymous
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
reqEditorRole := middleware.ReqEditorRole
reqOrgAdmin := middleware.ReqOrgAdmin
reqCanAccessTeams := middleware.AdminOrFeatureEnabled(hs.Cfg.EditorsCanAdmin)
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := acmiddleware.Middleware(hs.AccessControl)
quota := middleware.Quota(hs.QuotaService)
bind := binding.Bind
r := hs.RouteRegister
// not logged in views
r.Get("/logout", hs.Logout)
r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), routing.Wrap(hs.LoginPost))
r.Get("/login/:name", quota("session"), hs.OAuthLogin)
r.Get("/login", hs.LoginView)
r.Get("/invite/:code", hs.Index)
// authed views
r.Get("/", reqSignedIn, hs.Index)
r.Get("/profile/", reqSignedInNoAnonymous, hs.Index)
r.Get("/profile/password", reqSignedInNoAnonymous, hs.Index)
r.Get("/.well-known/change-password", redirectToChangePassword)
r.Get("/profile/switch-org/:id", reqSignedInNoAnonymous, hs.ChangeActiveOrgAndRedirectToHome)
r.Get("/org/", reqOrgAdmin, hs.Index)
r.Get("/org/new", reqGrafanaAdmin, hs.Index)
r.Get("/datasources/", reqOrgAdmin, hs.Index)
r.Get("/datasources/new", reqOrgAdmin, hs.Index)
r.Get("/datasources/edit/*", reqOrgAdmin, hs.Index)
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), hs.Index)
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
r.Get("/org/teams", reqCanAccessTeams, hs.Index)
r.Get("/org/teams/*", reqCanAccessTeams, hs.Index)
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
r.Get("/dashboard/import/", reqSignedIn, hs.Index)
r.Get("/configuration", reqGrafanaAdmin, hs.Index)
r.Get("/admin", reqGrafanaAdmin, hs.Index)
r.Get("/admin/settings", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)), hs.Index)
r.Get("/admin/users", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), hs.Index)
r.Get("/admin/users/create", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
r.Get("/admin/users/edit/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead)), hs.Index)
r.Get("/admin/orgs", reqGrafanaAdmin, hs.Index)
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, hs.Index)
r.Get("/admin/stats", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)), hs.Index)
r.Get("/admin/ldap", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), hs.Index)
r.Get("/styleguide", reqSignedIn, hs.Index)
r.Get("/plugins", reqSignedIn, hs.Index)
r.Get("/plugins/:id/", reqSignedIn, hs.Index)
r.Get("/plugins/:id/edit", reqSignedIn, hs.Index) // deprecated
r.Get("/plugins/:id/page/:page", reqSignedIn, hs.Index)
r.Get("/a/:id/*", reqSignedIn, hs.Index) // App Root Page
r.Get("/a/:id", reqSignedIn, hs.Index)
r.Get("/d/:uid/:slug", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
r.Get("/d/:uid", reqSignedIn, redirectFromLegacyPanelEditURL, hs.Index)
r.Get("/dashboard/script/*", reqSignedIn, hs.Index)
r.Get("/dashboard/new", reqSignedIn, hs.Index)
r.Get("/dashboard-solo/snapshot/*", hs.Index)
r.Get("/d-solo/:uid/:slug", reqSignedIn, hs.Index)
r.Get("/d-solo/:uid", reqSignedIn, hs.Index)
r.Get("/dashboard-solo/script/*", reqSignedIn, hs.Index)
r.Get("/import/dashboard", reqSignedIn, hs.Index)
r.Get("/dashboards/", reqSignedIn, hs.Index)
r.Get("/dashboards/*", reqSignedIn, hs.Index)
Dashboard: Allow shortlink generation (#27409) * intial frontend resolution/redirection logic * backend scaffolding * enough of the frontend to actually test end to end * bugfixes * add tests * cleanup * explore too hard for now * fix build * Docs: add docs * FE test * redirect directly from backend * validate incoming uids * add last_seen_at * format documentation * more documentation feedback * very shaky migration of get route to middleware * persist unix timestamps * add id, orgId to table * fixes for orgId scoping * whoops forgot the middleware * only redirect to absolute URLs under the AppUrl domain * move lookup route to /goto/:uid, stop manually setting 404 response code * renaming things according to PR feedback * tricky deletion * sneaky readd * fix test * more BE renaming * FE updates -- no more @ts-ignore hacking :) and accounting for subpath * Simplify code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Short URLs: Drop usage of bus Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * ShortURLService: Make injectable Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename file Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add handling of url parsing and creating of full shortURL to backend * Update test, remove unused imports * Update pkg/api/short_urls.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Add correct import * Pass context to short url service * Remove not needed error log * Rename dto and field to denote URL rather than path * Update api docs based on feedback/suggestion * Rename files to singular * Revert to send relative path to backend * Fixes after review * Return dto when creating short URL that includes the full url Use full url to provide shorten URL to the user * Fix after review * Fix relative url path when creating new short url Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ivana <ivana.huckova@gmail.com> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
5 years ago
r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index)
r.Get("/explore", authorize(func(c *models.ReqContext) {
if f, ok := reqSignedIn.(func(c *models.ReqContext)); ok {
f(c)
}
middleware.EnsureEditorOrViewerCanEdit(c)
}, ac.EvalPermission(ac.ActionDatasourcesExplore)), hs.Index)
r.Get("/playlists/", reqSignedIn, hs.Index)
r.Get("/playlists/*", reqSignedIn, hs.Index)
r.Get("/alerting/", reqSignedIn, hs.Index)
r.Get("/alerting/*", reqSignedIn, hs.Index)
// sign up
r.Get("/verify", hs.Index)
r.Get("/signup", hs.Index)
r.Get("/api/user/signup/options", routing.Wrap(GetSignUpOptions))
r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), routing.Wrap(SignUp))
r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), routing.Wrap(hs.SignUpStep2))
// invited
r.Get("/api/user/invite/:code", routing.Wrap(GetInviteInfoByCode))
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), routing.Wrap(hs.CompleteInvite))
// reset password
r.Get("/user/password/send-reset-email", reqNotSignedIn, hs.Index)
r.Get("/user/password/reset", hs.Index)
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), routing.Wrap(SendResetPasswordEmail))
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), routing.Wrap(ResetPassword))
// dashboard snapshots
r.Get("/dashboard/snapshot/*", reqNoAuth, hs.Index)
r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index)
// api renew session based on cookie
r.Get("/api/login/ping", quota("session"), routing.Wrap(hs.LoginAPIPing))
// expose plugin file system assets
r.Get("/public/plugins/:pluginId/*", hs.GetPluginAssets)
// authed api
r.Group("/api", func(apiRoute routing.RouteRegister) {
// user (signed in)
apiRoute.Group("/user", func(userRoute routing.RouteRegister) {
userRoute.Get("/", routing.Wrap(GetSignedInUser))
userRoute.Put("/", bind(models.UpdateUserCommand{}), routing.Wrap(UpdateSignedInUser))
userRoute.Post("/using/:id", routing.Wrap(UserSetUsingOrg))
userRoute.Get("/orgs", routing.Wrap(GetSignedInUserOrgList))
userRoute.Get("/teams", routing.Wrap(GetSignedInUserTeamList))
userRoute.Post("/stars/dashboard/:id", routing.Wrap(StarDashboard))
userRoute.Delete("/stars/dashboard/:id", routing.Wrap(UnstarDashboard))
userRoute.Put("/password", bind(models.ChangeUserPasswordCommand{}), routing.Wrap(ChangeUserPassword))
userRoute.Get("/quotas", routing.Wrap(GetUserQuotas))
userRoute.Put("/helpflags/:id", routing.Wrap(SetHelpFlag))
// For dev purpose
userRoute.Get("/helpflags/clear", routing.Wrap(ClearHelpFlags))
userRoute.Get("/preferences", routing.Wrap(GetUserPreferences))
userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), routing.Wrap(UpdateUserPreferences))
userRoute.Get("/auth-tokens", routing.Wrap(hs.GetUserAuthTokens))
userRoute.Post("/revoke-auth-token", bind(models.RevokeAuthTokenCmd{}), routing.Wrap(hs.RevokeUserAuthToken))
}, reqSignedInNoAnonymous)
// users (admin permission required)
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
userIDScope := ac.Scope("global", "users", ac.Parameter(":id"))
usersRoute.Get("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(SearchUsers))
usersRoute.Get("/search", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(SearchUsersWithPaging))
usersRoute.Get("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(GetUserByID))
usersRoute.Get("/:id/teams", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersTeamRead, userIDScope)), routing.Wrap(GetUserTeams))
usersRoute.Get("/:id/orgs", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com
usersRoute.Get("/lookup", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(GetUserByLoginOrEmail))
usersRoute.Put("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), bind(models.UpdateUserCommand{}), routing.Wrap(UpdateUser))
usersRoute.Post("/:id/using/:orgId", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(UpdateUserActiveOrg))
})
// team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Post("/", bind(models.CreateTeamCommand{}), routing.Wrap(hs.CreateTeam))
teamsRoute.Put("/:teamId", bind(models.UpdateTeamCommand{}), routing.Wrap(hs.UpdateTeam))
teamsRoute.Delete("/:teamId", routing.Wrap(hs.DeleteTeamByID))
teamsRoute.Get("/:teamId/members", routing.Wrap(hs.GetTeamMembers))
teamsRoute.Post("/:teamId/members", bind(models.AddTeamMemberCommand{}), routing.Wrap(hs.AddTeamMember))
teamsRoute.Put("/:teamId/members/:userId", bind(models.UpdateTeamMemberCommand{}), routing.Wrap(hs.UpdateTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", routing.Wrap(hs.RemoveTeamMember))
teamsRoute.Get("/:teamId/preferences", routing.Wrap(hs.GetTeamPreferences))
teamsRoute.Put("/:teamId/preferences", bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateTeamPreferences))
}, reqCanAccessTeams)
// team without requirement of user to be org admin
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Get("/:teamId", routing.Wrap(hs.GetTeamByID))
teamsRoute.Get("/search", routing.Wrap(hs.SearchTeams))
})
// org information available to all users.
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/", routing.Wrap(GetOrgCurrent))
orgRoute.Get("/quotas", routing.Wrap(GetOrgQuotas))
})
// current org
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
userIDScope := ac.Scope("users", ac.Parameter(":userId"))
orgRoute.Put("/", reqOrgAdmin, bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrgCurrent))
orgRoute.Put("/address", reqOrgAdmin, bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddressCurrent))
orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.SearchOrgUsersWithPaging))
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), bind(models.AddOrgUserCommand{}), routing.Wrap(AddOrgUserToCurrentOrg))
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(UpdateOrgUserForCurrentOrg))
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(RemoveOrgUserForCurrentOrg))
// invites
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(GetPendingOrgInvites))
orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), quota("user"), bind(dtos.AddInviteForm{}), routing.Wrap(AddOrgInvite))
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(RevokeInvite))
// prefs
orgRoute.Get("/preferences", reqOrgAdmin, routing.Wrap(GetOrgPreferences))
orgRoute.Put("/preferences", reqOrgAdmin, bind(dtos.UpdatePrefsCmd{}), routing.Wrap(UpdateOrgPreferences))
})
// current org without requirement of user to be org admin
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/users/lookup", routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
})
// create new org
apiRoute.Post("/orgs", quota("org"), bind(models.CreateOrgCommand{}), routing.Wrap(CreateOrg))
// search all orgs
apiRoute.Get("/orgs", reqGrafanaAdmin, routing.Wrap(SearchOrgs))
// orgs (admin routes)
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
userIDScope := ac.Scope("users", ac.Parameter(":userId"))
orgsRoute.Get("/", reqGrafanaAdmin, routing.Wrap(GetOrgByID))
orgsRoute.Put("/", reqGrafanaAdmin, bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrg))
orgsRoute.Put("/address", reqGrafanaAdmin, bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddress))
orgsRoute.Delete("/", reqGrafanaAdmin, routing.Wrap(DeleteOrgByID))
orgsRoute.Get("/users", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsers))
orgsRoute.Post("/users", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), bind(models.AddOrgUserCommand{}), routing.Wrap(AddOrgUser))
orgsRoute.Patch("/users/:userId", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(UpdateOrgUser))
orgsRoute.Delete("/users/:userId", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(RemoveOrgUser))
orgsRoute.Get("/quotas", reqGrafanaAdmin, routing.Wrap(GetOrgQuotas))
orgsRoute.Put("/quotas/:target", reqGrafanaAdmin, bind(models.UpdateOrgQuotaCmd{}), routing.Wrap(UpdateOrgQuota))
})
// orgs (admin routes)
apiRoute.Group("/orgs/name/:name", func(orgsRoute routing.RouteRegister) {
orgsRoute.Get("/", routing.Wrap(hs.GetOrgByName))
}, reqGrafanaAdmin)
// auth api keys
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
keysRoute.Get("/", routing.Wrap(GetAPIKeys))
keysRoute.Post("/", quota("api_key"), bind(models.AddApiKeyCommand{}), routing.Wrap(hs.AddAPIKey))
keysRoute.Delete("/:id", routing.Wrap(DeleteAPIKey))
}, reqOrgAdmin)
// Preferences
apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) {
prefRoute.Post("/set-home-dash", bind(models.SavePreferencesCommand{}), routing.Wrap(SetHomeDashboard))
})
// Data sources
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
datasourceRoute.Get("/", routing.Wrap(hs.GetDataSources))
datasourceRoute.Post("/", quota("data_source"), bind(models.AddDataSourceCommand{}), routing.Wrap(AddDataSource))
datasourceRoute.Put("/:id", bind(models.UpdateDataSourceCommand{}), routing.Wrap(hs.UpdateDataSource))
datasourceRoute.Delete("/:id", routing.Wrap(hs.DeleteDataSourceById))
datasourceRoute.Delete("/uid/:uid", routing.Wrap(hs.DeleteDataSourceByUID))
datasourceRoute.Delete("/name/:name", routing.Wrap(hs.DeleteDataSourceByName))
datasourceRoute.Get("/:id", routing.Wrap(GetDataSourceById))
datasourceRoute.Get("/uid/:uid", routing.Wrap(GetDataSourceByUID))
datasourceRoute.Get("/name/:name", routing.Wrap(GetDataSourceByName))
}, reqOrgAdmin)
apiRoute.Get("/datasources/id/:name", routing.Wrap(GetDataSourceIdByName), reqSignedIn)
apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList))
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", routing.Wrap(hs.GetPluginMarkdown))
apiRoute.Get("/plugins/:pluginId/health", routing.Wrap(hs.CheckHealth))
apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource)
apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource)
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList))
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Post("/:pluginId/install", bind(dtos.InstallPluginCommand{}), routing.Wrap(hs.InstallPlugin))
pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin))
}, reqGrafanaAdmin)
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Get("/:pluginId/dashboards/", routing.Wrap(hs.GetPluginDashboards))
pluginRoute.Post("/:pluginId/settings", bind(models.UpdatePluginSettingCmd{}), routing.Wrap(hs.UpdatePluginSetting))
pluginRoute.Get("/:pluginId/metrics", routing.Wrap(hs.CollectPluginMetrics))
}, reqOrgAdmin)
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
apiRoute.Any("/datasources/proxy/:id/*", reqSignedIn, hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/:id/resources", hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/resources/*", hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/health", routing.Wrap(hs.CheckDatasourceHealth))
// Folders
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
folderRoute.Get("/", routing.Wrap(hs.GetFolders))
folderRoute.Get("/id/:id", routing.Wrap(hs.GetFolderByID))
folderRoute.Post("/", bind(models.CreateFolderCommand{}), routing.Wrap(hs.CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) {
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
folderUidRoute.Get("/", routing.Wrap(hs.GetFolderByUID))
folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(hs.UpdateFolder))
folderUidRoute.Delete("/", routing.Wrap(hs.DeleteFolder))
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
folderPermissionRoute.Get("/", routing.Wrap(hs.GetFolderPermissionList))
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), routing.Wrap(hs.UpdateFolderPermissions))
})
})
})
// Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) {
dashboardRoute.Get("/uid/:uid", routing.Wrap(hs.GetDashboard))
PanelLibrary: adding library panels to Dashboard Api (#30278) * Wip: First naive impl * Chore: fix after merge * Chore: changes after PR comments * Chore: removes unused types * Chore: adds feature toggle * Refactor: adds library panels cleanup and connect when storing dashboards * Refactor: adds feature toggle * Update pkg/services/librarypanels/librarypanels.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/librarypanels/librarypanels.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Refactor: adds disconnect library panels when deleting a dashboard * Chore: changes after PR comments * Tests: adds tests for LoadLibraryPanelsForDashboard * Tests: adds tests for CleanLibraryPanelsForDashboard * Tests: adds tests for ConnectLibraryPanelsForDashboard * Tests: adds tests for DisconnectLibraryPanelsForDashboard and small refactor * Update pkg/services/librarypanels/librarypanels_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/librarypanels/librarypanels_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/librarypanels/librarypanels_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/librarypanels/librarypanels_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Refactor: deletes all connections in one call and connects all in the same transaction * Chore: adds better comments * Chore: changes after PR comments Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
dashboardRoute.Delete("/uid/:uid", routing.Wrap(hs.DeleteDashboardByUID))
dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), routing.Wrap(CalculateDashboardDiff))
dashboardRoute.Post("/trim", bind(models.TrimDashboardCommand{}), routing.Wrap(hs.TrimDashboard))
dashboardRoute.Post("/db", bind(models.SaveDashboardCommand{}), routing.Wrap(hs.PostDashboard))
dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard))
dashboardRoute.Get("/tags", GetDashboardTags)
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), routing.Wrap(hs.ImportDashboard))
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) {
dashIdRoute.Get("/versions", routing.Wrap(GetDashboardVersions))
dashIdRoute.Get("/versions/:id", routing.Wrap(GetDashboardVersion))
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), routing.Wrap(hs.RestoreDashboardVersion))
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) {
dashboardPermissionRoute.Get("/", routing.Wrap(hs.GetDashboardPermissionList))
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), routing.Wrap(hs.UpdateDashboardPermissions))
})
})
})
9 years ago
// Dashboard snapshots
apiRoute.Group("/dashboard/snapshots", func(dashboardRoute routing.RouteRegister) {
dashboardRoute.Get("/", routing.Wrap(SearchDashboardSnapshots))
9 years ago
})
// Playlist
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
playlistRoute.Get("/", routing.Wrap(SearchPlaylists))
playlistRoute.Get("/:id", ValidateOrgPlaylist, routing.Wrap(GetPlaylist))
playlistRoute.Get("/:id/items", ValidateOrgPlaylist, routing.Wrap(GetPlaylistItems))
playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, routing.Wrap(GetPlaylistDashboards))
playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, routing.Wrap(DeletePlaylist))
playlistRoute.Put("/:id", reqEditorRole, bind(models.UpdatePlaylistCommand{}), ValidateOrgPlaylist, routing.Wrap(UpdatePlaylist))
playlistRoute.Post("/", reqEditorRole, bind(models.CreatePlaylistCommand{}), routing.Wrap(CreatePlaylist))
})
// Search
apiRoute.Get("/search/sorting", routing.Wrap(hs.ListSortOptions))
apiRoute.Get("/search/", routing.Wrap(Search))
// metrics
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics))
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, routing.Wrap(GenerateSQLTestData))
apiRoute.Get("/tsdb/testdata/random-walk", routing.Wrap(hs.GetTestDataRandomWalk))
// DataSource w/ expressions
apiRoute.Post("/ds/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2))
apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) {
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), routing.Wrap(hs.AlertTest))
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), routing.Wrap(PauseAlert))
alertsRoute.Get("/:alertId", ValidateOrgAlert, routing.Wrap(GetAlert))
alertsRoute.Get("/", routing.Wrap(GetAlerts))
alertsRoute.Get("/states-for-dashboard", routing.Wrap(GetAlertStatesForDashboard))
})
apiRoute.Get("/alert-notifiers", reqEditorRole, routing.Wrap(
GetAlertNotifiers(hs.MultiOrgAlertmanager != nil && hs.Cfg.IsNgAlertEnabled())),
)
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
alertNotifications.Get("/", routing.Wrap(GetAlertNotifications))
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest))
alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), routing.Wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(UpdateAlertNotification))
alertNotifications.Get("/:notificationId", routing.Wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification))
alertNotifications.Get("/uid/:uid", routing.Wrap(GetAlertNotificationByUID))
alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(UpdateAlertNotificationByUID))
alertNotifications.Delete("/uid/:uid", routing.Wrap(DeleteAlertNotificationByUID))
}, reqEditorRole)
// alert notifications without requirement of user to be org editor
apiRoute.Group("/alert-notifications", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/lookup", routing.Wrap(GetAlertNotificationLookup))
})
apiRoute.Get("/annotations", routing.Wrap(GetAnnotations))
apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), routing.Wrap(DeleteAnnotations))
apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) {
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), routing.Wrap(PostAnnotation))
annotationsRoute.Delete("/:annotationId", routing.Wrap(DeleteAnnotationByID))
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), routing.Wrap(UpdateAnnotation))
annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), routing.Wrap(PatchAnnotation))
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), routing.Wrap(PostGraphiteAnnotation))
annotationsRoute.Get("/tags", routing.Wrap(GetAnnotationTags))
})
apiRoute.Post("/frontend-metrics", bind(metrics.PostFrontendMetricsCommand{}), routing.Wrap(hs.PostFrontendMetrics))
apiRoute.Group("/live", func(liveRoute routing.RouteRegister) {
// the channel path is in the name
liveRoute.Post("/publish", bind(dtos.LivePublishCmd{}), routing.Wrap(hs.Live.HandleHTTPPublish))
// POST influx line protocol
liveRoute.Post("/push/:streamId", hs.LivePushGateway.Handle)
// List available streams and fields
liveRoute.Get("/list", routing.Wrap(hs.Live.HandleListHTTP))
// Some channels may have info
liveRoute.Get("/info/*", routing.Wrap(hs.Live.HandleInfoHTTP))
})
Dashboard: Allow shortlink generation (#27409) * intial frontend resolution/redirection logic * backend scaffolding * enough of the frontend to actually test end to end * bugfixes * add tests * cleanup * explore too hard for now * fix build * Docs: add docs * FE test * redirect directly from backend * validate incoming uids * add last_seen_at * format documentation * more documentation feedback * very shaky migration of get route to middleware * persist unix timestamps * add id, orgId to table * fixes for orgId scoping * whoops forgot the middleware * only redirect to absolute URLs under the AppUrl domain * move lookup route to /goto/:uid, stop manually setting 404 response code * renaming things according to PR feedback * tricky deletion * sneaky readd * fix test * more BE renaming * FE updates -- no more @ts-ignore hacking :) and accounting for subpath * Simplify code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Short URLs: Drop usage of bus Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * ShortURLService: Make injectable Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename file Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Add handling of url parsing and creating of full shortURL to backend * Update test, remove unused imports * Update pkg/api/short_urls.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Add correct import * Pass context to short url service * Remove not needed error log * Rename dto and field to denote URL rather than path * Update api docs based on feedback/suggestion * Rename files to singular * Revert to send relative path to backend * Fixes after review * Return dto when creating short URL that includes the full url Use full url to provide shorten URL to the user * Fix after review * Fix relative url path when creating new short url Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ivana <ivana.huckova@gmail.com> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
5 years ago
// short urls
apiRoute.Post("/short-urls", bind(dtos.CreateShortURLCmd{}), routing.Wrap(hs.createShortURL))
}, reqSignedIn)
// admin api
r.Group("/api/admin", func(adminRoute routing.RouteRegister) {
adminRoute.Get("/settings", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)), routing.Wrap(hs.AdminGetSettings))
adminRoute.Get("/stats", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)), routing.Wrap(AdminGetStats))
adminRoute.Post("/pause-all-alerts", reqGrafanaAdmin, bind(dtos.PauseAllAlertsCommand{}), routing.Wrap(PauseAllAlerts))
adminRoute.Post("/provisioning/dashboards/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDashboards)), routing.Wrap(hs.AdminProvisioningReloadDashboards))
adminRoute.Post("/provisioning/plugins/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersPlugins)), routing.Wrap(hs.AdminProvisioningReloadPlugins))
adminRoute.Post("/provisioning/datasources/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDatasources)), routing.Wrap(hs.AdminProvisioningReloadDatasources))
adminRoute.Post("/provisioning/notifications/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersNotifications)), routing.Wrap(hs.AdminProvisioningReloadNotifications))
adminRoute.Post("/ldap/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPConfigReload)), routing.Wrap(hs.ReloadLDAPCfg))
adminRoute.Post("/ldap/sync/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersSync)), routing.Wrap(hs.PostSyncUserWithLDAP))
adminRoute.Get("/ldap/:username", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPUsersRead)), routing.Wrap(hs.GetUserFromLDAP))
adminRoute.Get("/ldap/status", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), routing.Wrap(hs.GetLDAPStatus))
})
// Administering users
r.Group("/api/admin/users", func(adminUserRoute routing.RouteRegister) {
userIDScope := ac.Scope("global", "users", ac.Parameter(":id"))
adminUserRoute.Post("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersCreate)), bind(dtos.AdminCreateUserForm{}), routing.Wrap(hs.AdminCreateUser))
adminUserRoute.Put("/:id/password", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), bind(dtos.AdminUpdateUserPasswordForm{}), routing.Wrap(AdminUpdateUserPassword))
adminUserRoute.Put("/:id/permissions", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), bind(dtos.AdminUpdateUserPermissionsForm{}), routing.Wrap(hs.AdminUpdateUserPermissions))
adminUserRoute.Delete("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(AdminDeleteUser))
adminUserRoute.Post("/:id/disable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser))
adminUserRoute.Post("/:id/enable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(AdminEnableUser))
adminUserRoute.Get("/:id/quotas", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(GetUserQuotas))
adminUserRoute.Put("/:id/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), bind(models.UpdateUserQuotaCmd{}), routing.Wrap(UpdateUserQuota))
adminUserRoute.Post("/:id/logout", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser))
adminUserRoute.Get("/:id/auth-tokens", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens))
adminUserRoute.Post("/:id/revoke-auth-token", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), bind(models.RevokeAuthTokenCmd{}), routing.Wrap(hs.AdminRevokeUserAuthToken))
})
// rendering
r.Get("/render/*", reqSignedIn, hs.RenderToPng)
// grafana.net proxy
r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
// Gravatar service.
avatarCacheServer := avatar.NewCacheServer(hs.Cfg)
r.Get("/avatar/:hash", avatarCacheServer.Handler)
// Snapshots
r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
r.Get("/api/snapshot/shared-options/", reqSignedIn, GetSharingOptions)
r.Get("/api/snapshots/:key", routing.Wrap(GetDashboardSnapshot))
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, routing.Wrap(DeleteDashboardSnapshotByDeleteKey))
r.Delete("/api/snapshots/:key", reqEditorRole, routing.Wrap(DeleteDashboardSnapshot))
// Frontend logs
sourceMapStore := frontendlogging.NewSourceMapStore(hs.Cfg, hs.PluginManager, frontendlogging.ReadSourceMapFromFS)
r.Post("/log", middleware.RateLimit(hs.Cfg.Sentry.EndpointRPS, hs.Cfg.Sentry.EndpointBurst, time.Now),
bind(frontendlogging.FrontendSentryEvent{}), routing.Wrap(NewFrontendLogMessageHandler(sourceMapStore)))
}