mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 18:44:54 +08:00
Plugins: Fix catalog permissions for org and server admins (#37504)
* simplify toggle + add link to server admin * feat(catalog): org admins can configure plugin apps, cannot install/uninstall plugins * fix(catalog): dont show buttons if user doesn't have install permissions * feat(catalog): cater for accessing catalog via /plugins and /admin/plugins * feat(catalog): use location for list links and match.url to define breadcrumb links * test(catalog): mock isGrafanaAdmin for PluginDetails tests * test(catalog): preserve default bootdata in PluginDetails mock * refactor(catalog): move orgAdmin check out of state and make easier to reason with Co-authored-by: Will Browne <will.browne@grafana.com>
This commit is contained in:
@ -287,12 +287,10 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource)
|
apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource)
|
||||||
apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList))
|
apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList))
|
||||||
|
|
||||||
if hs.Cfg.PluginAdminEnabled {
|
|
||||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||||
pluginRoute.Post("/:pluginId/install", bind(dtos.InstallPluginCommand{}), routing.Wrap(hs.InstallPlugin))
|
pluginRoute.Post("/:pluginId/install", bind(dtos.InstallPluginCommand{}), routing.Wrap(hs.InstallPlugin))
|
||||||
pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin))
|
pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin))
|
||||||
}, reqGrafanaAdmin)
|
}, reqGrafanaAdmin)
|
||||||
}
|
|
||||||
|
|
||||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||||
pluginRoute.Get("/:pluginId/dashboards/", routing.Wrap(hs.GetPluginDashboards))
|
pluginRoute.Get("/:pluginId/dashboards/", routing.Wrap(hs.GetPluginDashboards))
|
||||||
|
@ -247,7 +247,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
|||||||
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
|
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
|
||||||
"sentry": hs.Cfg.Sentry,
|
"sentry": hs.Cfg.Sentry,
|
||||||
"pluginCatalogURL": hs.Cfg.PluginCatalogURL,
|
"pluginCatalogURL": hs.Cfg.PluginCatalogURL,
|
||||||
"pluginAdminEnabled": (c.IsGrafanaAdmin || hs.Cfg.PluginAdminExternalManageEnabled) && hs.Cfg.PluginAdminEnabled,
|
"pluginAdminEnabled": hs.Cfg.PluginAdminEnabled,
|
||||||
"pluginAdminExternalManageEnabled": hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
|
"pluginAdminExternalManageEnabled": hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
|
||||||
"expressionsEnabled": hs.Cfg.ExpressionsEnabled,
|
"expressionsEnabled": hs.Cfg.ExpressionsEnabled,
|
||||||
"awsAllowedAuthProviders": hs.Cfg.AWSAllowedAuthProviders,
|
"awsAllowedAuthProviders": hs.Cfg.AWSAllowedAuthProviders,
|
||||||
|
@ -388,6 +388,12 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hs.Cfg.PluginAdminEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.ActionPluginsManage) {
|
||||||
|
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
|
||||||
|
Text: "Plugins", Id: "admin-plugins", Url: hs.Cfg.AppSubURL + "/admin/plugins", Icon: "plug",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return adminNavLinks
|
return adminNavLinks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,9 @@ const (
|
|||||||
// Datasources actions
|
// Datasources actions
|
||||||
ActionDatasourcesExplore = "datasources:explore"
|
ActionDatasourcesExplore = "datasources:explore"
|
||||||
|
|
||||||
|
// Plugin actions
|
||||||
|
ActionPluginsManage = "plugins:manage"
|
||||||
|
|
||||||
// Global Scopes
|
// Global Scopes
|
||||||
ScopeGlobalUsersAll = "global:users:*"
|
ScopeGlobalUsersAll = "global:users:*"
|
||||||
|
|
||||||
|
@ -94,6 +94,11 @@ export const InstallControls = ({ plugin, isInflight, hasUpdate, isInstalled, ha
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasPermission) {
|
||||||
|
const message = `You need server admin privileges to ${isInstalled ? 'uninstall' : 'install'} this plugin.`;
|
||||||
|
return <div className={styles.message}>{message}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup height="auto">
|
<HorizontalGroup height="auto">
|
||||||
@ -122,7 +127,6 @@ export const InstallControls = ({ plugin, isInflight, hasUpdate, isInstalled, ha
|
|||||||
Please refresh your browser window before using this plugin.
|
Please refresh your browser window before using this plugin.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!hasPermission && <div className={styles.message}>You need admin privileges to manage this plugin.</div>}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
@ -149,7 +153,6 @@ export const InstallControls = ({ plugin, isInflight, hasUpdate, isInstalled, ha
|
|||||||
<Button disabled={isInflight || !hasPermission} onClick={onInstall}>
|
<Button disabled={isInflight || !hasPermission} onClick={onInstall}>
|
||||||
{isInflight ? 'Installing' : 'Install'}
|
{isInflight ? 'Installing' : 'Install'}
|
||||||
</Button>
|
</Button>
|
||||||
{!hasPermission && <div className={styles.message}>You need admin privileges to install this plugin.</div>}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { Card } from '../components/Card';
|
import { Card } from '../components/Card';
|
||||||
import { Grid } from '../components/Grid';
|
import { Grid } from '../components/Grid';
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ interface Props {
|
|||||||
|
|
||||||
export const PluginList = ({ plugins }: Props) => {
|
export const PluginList = ({ plugins }: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
@ -23,7 +25,7 @@ export const PluginList = ({ plugins }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={`${id}`}
|
key={`${id}`}
|
||||||
href={`/plugins/${id}`}
|
href={`${location.pathname}/${id}`}
|
||||||
image={
|
image={
|
||||||
<PluginLogo
|
<PluginLogo
|
||||||
src={plugin.info.logos.small}
|
src={plugin.info.logos.small}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { gt } from 'semver';
|
import { gt } from 'semver';
|
||||||
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { CatalogPlugin, CatalogPluginDetails, LocalPlugin, Plugin, Version, PluginFilter } from './types';
|
import { CatalogPlugin, CatalogPluginDetails, LocalPlugin, Plugin, Version, PluginFilter } from './types';
|
||||||
|
|
||||||
export function isGrafanaAdmin(): boolean {
|
export function isGrafanaAdmin(): boolean {
|
||||||
return config.bootData.user.isGrafanaAdmin;
|
return config.bootData.user.isGrafanaAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOrgAdmin() {
|
||||||
|
return contextSrv.hasRole('Admin');
|
||||||
|
}
|
||||||
|
|
||||||
export function mapRemoteToCatalog(plugin: Plugin): CatalogPlugin {
|
export function mapRemoteToCatalog(plugin: Plugin): CatalogPlugin {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
@ -2,7 +2,7 @@ import { useReducer, useEffect } from 'react';
|
|||||||
import { PluginType, PluginIncludeType } from '@grafana/data';
|
import { PluginType, PluginIncludeType } from '@grafana/data';
|
||||||
import { api } from '../api';
|
import { api } from '../api';
|
||||||
import { loadPlugin } from '../../PluginPage';
|
import { loadPlugin } from '../../PluginPage';
|
||||||
import { getCatalogPluginDetails, isGrafanaAdmin } from '../helpers';
|
import { getCatalogPluginDetails, isOrgAdmin } from '../helpers';
|
||||||
import { ActionTypes, PluginDetailsActions, PluginDetailsState } from '../types';
|
import { ActionTypes, PluginDetailsActions, PluginDetailsState } from '../types';
|
||||||
|
|
||||||
const defaultTabs = [{ label: 'Overview' }, { label: 'Version history' }];
|
const defaultTabs = [{ label: 'Overview' }, { label: 'Version history' }];
|
||||||
@ -10,7 +10,6 @@ const defaultTabs = [{ label: 'Overview' }, { label: 'Version history' }];
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
hasInstalledPanel: false,
|
hasInstalledPanel: false,
|
||||||
hasUpdate: false,
|
hasUpdate: false,
|
||||||
isAdmin: isGrafanaAdmin(),
|
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
isInflight: false,
|
isInflight: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -87,6 +86,7 @@ const reducer = (state: PluginDetailsState, action: PluginDetailsActions) => {
|
|||||||
|
|
||||||
export const usePluginDetails = (id: string) => {
|
export const usePluginDetails = (id: string) => {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
|
const userCanConfigurePlugins = isOrgAdmin();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPlugin = async () => {
|
const fetchPlugin = async () => {
|
||||||
@ -125,7 +125,7 @@ export const usePluginDetails = (id: string) => {
|
|||||||
const pluginConfig = state.pluginConfig;
|
const pluginConfig = state.pluginConfig;
|
||||||
const tabs: Array<{ label: string }> = [...defaultTabs];
|
const tabs: Array<{ label: string }> = [...defaultTabs];
|
||||||
|
|
||||||
if (pluginConfig && state.isAdmin) {
|
if (pluginConfig && userCanConfigurePlugins) {
|
||||||
if (pluginConfig.meta.type === PluginType.app) {
|
if (pluginConfig.meta.type === PluginType.app) {
|
||||||
if (pluginConfig.angularConfigCtrl) {
|
if (pluginConfig.angularConfigCtrl) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
@ -149,7 +149,7 @@ export const usePluginDetails = (id: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch({ type: ActionTypes.UPDATE_TABS, payload: tabs });
|
dispatch({ type: ActionTypes.UPDATE_TABS, payload: tabs });
|
||||||
}, [state.isAdmin, state.pluginConfig, id]);
|
}, [userCanConfigurePlugins, state.pluginConfig, id]);
|
||||||
|
|
||||||
return { state, dispatch };
|
return { state, dispatch };
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { render, RenderResult, waitFor } from '@testing-library/react';
|
import { render, RenderResult, waitFor } from '@testing-library/react';
|
||||||
import BrowsePage from './Browse';
|
|
||||||
import { locationService } from '@grafana/runtime';
|
|
||||||
import { configureStore } from 'app/store/configureStore';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { LocalPlugin, Plugin } from '../types';
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import BrowsePage from './Browse';
|
||||||
|
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||||
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
import { LocalPlugin, Plugin, PluginAdminRoutes } from '../types';
|
||||||
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
@ -27,11 +28,14 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
function setup(path = '/plugins'): RenderResult {
|
function setup(path = '/plugins'): RenderResult {
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
locationService.push(path);
|
locationService.push(path);
|
||||||
|
const props = getRouteComponentProps({
|
||||||
|
route: { routeName: PluginAdminRoutes.Home } as any,
|
||||||
|
});
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router history={locationService.getHistory()}>
|
<Router history={locationService.getHistory()}>
|
||||||
<BrowsePage />
|
<BrowsePage {...props} />
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
@ -4,11 +4,11 @@ import { SelectableValue, dateTimeParse, GrafanaTheme2 } from '@grafana/data';
|
|||||||
import { LoadingPlaceholder, Select, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
import { LoadingPlaceholder, Select, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { locationSearchToObject } from '@grafana/runtime';
|
import { locationSearchToObject } from '@grafana/runtime';
|
||||||
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
import { PluginList } from '../components/PluginList';
|
import { PluginList } from '../components/PluginList';
|
||||||
import { SearchField } from '../components/SearchField';
|
import { SearchField } from '../components/SearchField';
|
||||||
import { useHistory } from '../hooks/useHistory';
|
import { useHistory } from '../hooks/useHistory';
|
||||||
import { CatalogPlugin } from '../types';
|
import { CatalogPlugin, PluginAdminRoutes } from '../types';
|
||||||
import { Page as PluginPage } from '../components/Page';
|
import { Page as PluginPage } from '../components/Page';
|
||||||
import { HorizontalGroup } from '../components/HorizontalGroup';
|
import { HorizontalGroup } from '../components/HorizontalGroup';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
@ -17,10 +17,11 @@ import { useSelector } from 'react-redux';
|
|||||||
import { StoreState } from 'app/types/store';
|
import { StoreState } from 'app/types/store';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
|
|
||||||
export default function Browse(): ReactElement | null {
|
export default function Browse({ route }: GrafanaRouteComponentProps): ReactElement | null {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const query = locationSearchToObject(location.search);
|
const query = locationSearchToObject(location.search);
|
||||||
const navModel = useSelector((state: StoreState) => getNavModel(state.navIndex, 'plugins'));
|
const navModelId = getNavModelId(route.routeName);
|
||||||
|
const navModel = useSelector((state: StoreState) => getNavModel(state.navIndex, navModelId));
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const q = (query.q as string) ?? '';
|
const q = (query.q as string) ?? '';
|
||||||
@ -127,6 +128,16 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Because the component is used under multiple paths (/plugins and /admin/plugins) we need to get
|
||||||
|
// the correct navModel from the store
|
||||||
|
const getNavModelId = (routeName?: string) => {
|
||||||
|
if (routeName === PluginAdminRoutes.HomeAdmin || routeName === PluginAdminRoutes.BrowseAdmin) {
|
||||||
|
return 'admin-plugins';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'plugins';
|
||||||
|
};
|
||||||
|
|
||||||
const sorters: { [name: string]: (a: CatalogPlugin, b: CatalogPlugin) => number } = {
|
const sorters: { [name: string]: (a: CatalogPlugin, b: CatalogPlugin) => number } = {
|
||||||
name: (a: CatalogPlugin, b: CatalogPlugin) => a.name.localeCompare(b.name),
|
name: (a: CatalogPlugin, b: CatalogPlugin) => a.name.localeCompare(b.name),
|
||||||
updated: (a: CatalogPlugin, b: CatalogPlugin) =>
|
updated: (a: CatalogPlugin, b: CatalogPlugin) =>
|
||||||
|
@ -31,6 +31,13 @@ jest.mock('@grafana/runtime', () => {
|
|||||||
}),
|
}),
|
||||||
config: {
|
config: {
|
||||||
...original.config,
|
...original.config,
|
||||||
|
bootData: {
|
||||||
|
...original.config.bootData,
|
||||||
|
user: {
|
||||||
|
...original.config.bootData.user,
|
||||||
|
isGrafanaAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
buildInfo: {
|
buildInfo: {
|
||||||
...original.config.buildInfo,
|
...original.config.buildInfo,
|
||||||
version: 'v7.5.0',
|
version: 'v7.5.0',
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2, TabsBar, TabContent, Tab, Icon, Alert } from '@grafana/ui';
|
import { useStyles2, TabsBar, TabContent, Tab, Icon, Alert } from '@grafana/ui';
|
||||||
|
|
||||||
@ -34,6 +33,7 @@ export default function PluginDetails({ match }: PluginDetailsProps): JSX.Elemen
|
|||||||
} = state;
|
} = state;
|
||||||
const tab = tabs[activeTab];
|
const tab = tabs[activeTab];
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const breadcrumbHref = match.url.substring(0, match.url.lastIndexOf('/'));
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -66,13 +66,13 @@ export default function PluginDetails({ match }: PluginDetailsProps): JSX.Elemen
|
|||||||
className={css`
|
className={css`
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
`}
|
`}
|
||||||
href={'/plugins'}
|
href={breadcrumbHref}
|
||||||
>
|
>
|
||||||
Plugins
|
Plugins
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={`/plugins/${pluginId}`} aria-current="page">
|
<a href={`${match.url}`} aria-current="page">
|
||||||
{plugin.name}
|
{plugin.name}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
import { GrafanaPlugin, PluginMeta } from '@grafana/data';
|
import { GrafanaPlugin, PluginMeta } from '@grafana/data';
|
||||||
export type PluginTypeCode = 'app' | 'panel' | 'datasource';
|
export type PluginTypeCode = 'app' | 'panel' | 'datasource';
|
||||||
|
|
||||||
|
export enum PluginAdminRoutes {
|
||||||
|
Home = 'plugins-home',
|
||||||
|
Browse = 'plugins-browse',
|
||||||
|
Details = 'plugins-details',
|
||||||
|
HomeAdmin = 'plugins-home-admin',
|
||||||
|
BrowseAdmin = 'plugins-browse-admin',
|
||||||
|
DetailsAdmin = 'plugins-details-admin',
|
||||||
|
}
|
||||||
|
|
||||||
export interface CatalogPlugin {
|
export interface CatalogPlugin {
|
||||||
description: string;
|
description: string;
|
||||||
downloads: number;
|
downloads: number;
|
||||||
@ -137,7 +147,6 @@ export interface Org {
|
|||||||
export interface PluginDetailsState {
|
export interface PluginDetailsState {
|
||||||
hasInstalledPanel: boolean;
|
hasInstalledPanel: boolean;
|
||||||
hasUpdate: boolean;
|
hasUpdate: boolean;
|
||||||
isAdmin: boolean;
|
|
||||||
isInstalled: boolean;
|
isInstalled: boolean;
|
||||||
isInflight: boolean;
|
isInflight: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -1,6 +1,26 @@
|
|||||||
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
|
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
import { RouteDescriptor } from 'app/core/navigation/types';
|
import { RouteDescriptor } from 'app/core/navigation/types';
|
||||||
|
import { isGrafanaAdmin } from './admin/helpers';
|
||||||
|
import { PluginAdminRoutes } from './admin/types';
|
||||||
|
|
||||||
|
const pluginAdminRoutes = [
|
||||||
|
{
|
||||||
|
path: '/plugins',
|
||||||
|
routeName: PluginAdminRoutes.Home,
|
||||||
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/plugins/browse',
|
||||||
|
routeName: PluginAdminRoutes.Browse,
|
||||||
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/plugins/:pluginId/',
|
||||||
|
routeName: PluginAdminRoutes.Details,
|
||||||
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './admin/pages/PluginDetails')),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function getPluginsAdminRoutes(cfg = config): RouteDescriptor[] {
|
export function getPluginsAdminRoutes(cfg = config): RouteDescriptor[] {
|
||||||
if (!cfg.pluginAdminEnabled) {
|
if (!cfg.pluginAdminEnabled) {
|
||||||
@ -22,18 +42,26 @@ export function getPluginsAdminRoutes(cfg = config): RouteDescriptor[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGrafanaAdmin()) {
|
||||||
return [
|
return [
|
||||||
|
...pluginAdminRoutes,
|
||||||
{
|
{
|
||||||
path: '/plugins',
|
path: '/admin/plugins',
|
||||||
|
routeName: PluginAdminRoutes.HomeAdmin,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/plugins/browse',
|
path: '/admin/plugins/browse',
|
||||||
|
routeName: PluginAdminRoutes.BrowseAdmin,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './admin/pages/Browse')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/plugins/:pluginId/',
|
path: '/admin/plugins/:pluginId/',
|
||||||
|
routeName: PluginAdminRoutes.DetailsAdmin,
|
||||||
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './admin/pages/PluginDetails')),
|
component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './admin/pages/PluginDetails')),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginAdminRoutes;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user