}
text={ Panels}
/>
}
text={ Data sources}
/>
}
text={ Apps}
/>
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/pages/Library.tsx b/plugins-bundled/internal/plugin-admin-app/src/pages/Library.tsx
similarity index 72%
rename from plugins-bundled/internal/plugin-catalog-app/src/pages/Library.tsx
rename to plugins-bundled/internal/plugin-admin-app/src/pages/Library.tsx
index 3ed89817bb3..620dc14d286 100644
--- a/plugins-bundled/internal/plugin-catalog-app/src/pages/Library.tsx
+++ b/plugins-bundled/internal/plugin-admin-app/src/pages/Library.tsx
@@ -1,17 +1,22 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { css } from '@emotion/css';
-import { GrafanaTheme2 } from '@grafana/data';
+import { AppRootProps, GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { PLUGIN_ROOT } from '../constants';
import { PluginList } from '../components/PluginList';
import { usePlugins } from '../hooks/usePlugins';
import { Page } from 'components/Page';
import { Loader } from 'components/Loader';
+import { CatalogTab, getCatalogNavModel } from './nav';
-export const Library = () => {
+export const Library = ({ meta, onNavChanged, basename }: AppRootProps) => {
const { isLoading, items, installedPlugins } = usePlugins();
const styles = useStyles2(getStyles);
+ useEffect(() => {
+ onNavChanged(getCatalogNavModel(CatalogTab.Browse, basename));
+ }, [onNavChanged, basename]);
+
const filteredPlugins = items.filter((plugin) => !!installedPlugins.find((_) => _.id === plugin.slug));
if (isLoading) {
@@ -26,7 +31,7 @@ export const Library = () => {
) : (
You haven't installed any plugins. Browse the{' '}
-
+
catalog
{' '}
for plugins to install.
diff --git a/plugins-bundled/internal/plugin-admin-app/src/pages/NotEnabed.tsx b/plugins-bundled/internal/plugin-admin-app/src/pages/NotEnabed.tsx
new file mode 100644
index 00000000000..9ebc9e3137e
--- /dev/null
+++ b/plugins-bundled/internal/plugin-admin-app/src/pages/NotEnabed.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Page } from 'components/Page';
+import { AppRootProps, NavModelItem } from '@grafana/data';
+
+export const NotEnabled = ({ onNavChanged }: AppRootProps) => {
+ const node: NavModelItem = {
+ id: 'not-found',
+ text: 'The plugin catalog is not enabled',
+ icon: 'exclamation-triangle',
+ url: 'not-found',
+ };
+ onNavChanged({
+ node: node,
+ main: node,
+ });
+
+ return (
+
+ To enabled installing plugins, set the{' '}
+ Plugin Catalog instructions
+
+ );
+};
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/pages/PluginDetails.tsx b/plugins-bundled/internal/plugin-admin-app/src/pages/PluginDetails.tsx
similarity index 90%
rename from plugins-bundled/internal/plugin-catalog-app/src/pages/PluginDetails.tsx
rename to plugins-bundled/internal/plugin-admin-app/src/pages/PluginDetails.tsx
index dfe5af03057..6e5e656606f 100644
--- a/plugins-bundled/internal/plugin-catalog-app/src/pages/PluginDetails.tsx
+++ b/plugins-bundled/internal/plugin-admin-app/src/pages/PluginDetails.tsx
@@ -1,8 +1,9 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { css } from '@emotion/css';
import { AppRootProps, GrafanaTheme2 } from '@grafana/data';
import { useStyles2, TabsBar, TabContent, Tab, Icon } from '@grafana/ui';
+import { useParams } from 'react-router-dom';
import { VersionList } from '../components/VersionList';
import { InstallControls } from '../components/InstallControls';
@@ -11,13 +12,15 @@ import { usePlugin } from '../hooks/usePlugins';
import { Page } from 'components/Page';
import { Loader } from 'components/Loader';
-export const PluginDetails = ({ query }: AppRootProps) => {
- const { slug } = query;
+export const PluginDetails = ({ onNavChanged }: AppRootProps) => {
+ const { pluginId } = useParams<{ pluginId: string }>();
+
const [tabs, setTabs] = useState([
{ label: 'Overview', active: true },
{ label: 'Version history', active: false },
]);
- const { isLoading, local, remote, remoteVersions } = usePlugin(slug);
+
+ const { isLoading, local, remote, remoteVersions } = usePlugin(pluginId);
const styles = useStyles2(getStyles);
const description = remote?.description;
@@ -26,6 +29,10 @@ export const PluginDetails = ({ query }: AppRootProps) => {
const links = (local?.info?.links || remote?.json?.info?.links) ?? [];
const downloads = remote?.downloads;
+ useEffect(() => {
+ onNavChanged(undefined as any);
+ }, [onNavChanged]);
+
if (isLoading) {
return ;
}
@@ -34,7 +41,7 @@ export const PluginDetails = ({ query }: AppRootProps) => {

{
{remote?.name}
-
+
{remote?.orgName}
{links.map((link: any) => (
@@ -62,7 +69,7 @@ export const PluginDetails = ({ query }: AppRootProps) => {
{version &&
{version}}
{description}
- {remote &&
}
+ {remote &&
}
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/pages/index.ts b/plugins-bundled/internal/plugin-admin-app/src/pages/index.ts
similarity index 84%
rename from plugins-bundled/internal/plugin-catalog-app/src/pages/index.ts
rename to plugins-bundled/internal/plugin-admin-app/src/pages/index.ts
index 294726e7bd7..9206f6d6a07 100644
--- a/plugins-bundled/internal/plugin-catalog-app/src/pages/index.ts
+++ b/plugins-bundled/internal/plugin-admin-app/src/pages/index.ts
@@ -3,7 +3,6 @@ import { AppRootProps } from '@grafana/data';
import { Discover } from './Discover';
import { Browse } from './Browse';
import { PluginDetails } from './PluginDetails';
-import { OrgDetails } from './OrgDetails';
import { Library } from './Library';
export type PageDefinition = {
@@ -38,10 +37,4 @@ export const pages: PageDefinition[] = [
id: 'plugin',
text: 'Plugin',
},
- {
- component: OrgDetails,
- icon: 'file-alt',
- id: 'org',
- text: 'Organization',
- },
];
diff --git a/plugins-bundled/internal/plugin-admin-app/src/pages/nav.ts b/plugins-bundled/internal/plugin-admin-app/src/pages/nav.ts
new file mode 100644
index 00000000000..f8c6485c4d9
--- /dev/null
+++ b/plugins-bundled/internal/plugin-admin-app/src/pages/nav.ts
@@ -0,0 +1,67 @@
+import { NavModel, NavModelItem } from '@grafana/data';
+
+export enum CatalogTab {
+ Browse = 'browse',
+ Discover = 'discover',
+ Library = 'library',
+}
+
+export function getCatalogNavModel(tab: CatalogTab, baseURL: string): NavModel {
+ const pages: NavModelItem[] = [];
+
+ if (!baseURL.endsWith('/')) {
+ baseURL += '/';
+ }
+
+ pages.push({
+ text: 'Browse',
+ icon: 'icon-gf icon-gf-apps',
+ url: `${baseURL}`,
+ id: CatalogTab.Browse,
+ });
+
+ // pages.push({
+ // text: 'Discover',
+ // icon: 'file-alt',
+ // url: `${baseURL}${CatalogTab.Discover}`,
+ // id: CatalogTab.Discover,
+ // });
+
+ pages.push({
+ text: 'Library',
+ icon: 'icon-gf icon-gf-apps',
+ url: `${baseURL}${CatalogTab.Library}`,
+ id: CatalogTab.Library,
+ });
+
+ const node: NavModelItem = {
+ text: 'Catalog',
+ icon: 'cog',
+ subTitle: 'Manage plugin installations',
+ breadcrumbs: [{ title: 'Plugins', url: 'plugins' }],
+ children: setActivePage(tab, pages, CatalogTab.Browse),
+ };
+
+ return {
+ node: node,
+ main: node,
+ };
+}
+
+function setActivePage(pageId: CatalogTab, pages: NavModelItem[], defaultPageId: CatalogTab): NavModelItem[] {
+ let found = false;
+ const selected = pageId || defaultPageId;
+ const changed = pages.map((p) => {
+ const active = !found && selected === p.id;
+ if (active) {
+ found = true;
+ }
+ return { ...p, active };
+ });
+
+ if (!found) {
+ changed[0].active = true;
+ }
+
+ return changed;
+}
diff --git a/plugins-bundled/internal/plugin-admin-app/src/plugin.json b/plugins-bundled/internal/plugin-admin-app/src/plugin.json
new file mode 100644
index 00000000000..2c275a22101
--- /dev/null
+++ b/plugins-bundled/internal/plugin-admin-app/src/plugin.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://github.com/grafana/grafana/raw/master/docs/sources/developers/plugins/plugin.schema.json",
+ "type": "app",
+ "name": "Plugin Admin",
+ "id": "grafana-plugin-admin-app",
+ "backend": false,
+ "autoEnabled": true,
+ "pinned": false,
+ "info": {
+ "author": {
+ "name": "Grafana Labs",
+ "url": "https://grafana.com"
+ },
+ "keywords": ["plugins"],
+ "logos": {
+ "small": "img/logo.svg",
+ "large": "img/logo.svg"
+ },
+ "links": [],
+ "screenshots": [],
+ "version": "%VERSION%",
+ "updated": "%TODAY%"
+ },
+ "dependencies": {
+ "grafanaDependency": ">=8.0.0",
+ "grafanaVersion": "8.0.x",
+ "plugins": []
+ }
+}
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/types.ts b/plugins-bundled/internal/plugin-admin-app/src/types.ts
similarity index 100%
rename from plugins-bundled/internal/plugin-catalog-app/src/types.ts
rename to plugins-bundled/internal/plugin-admin-app/src/types.ts
diff --git a/plugins-bundled/internal/plugin-catalog-app/tsconfig.json b/plugins-bundled/internal/plugin-admin-app/tsconfig.json
similarity index 100%
rename from plugins-bundled/internal/plugin-catalog-app/tsconfig.json
rename to plugins-bundled/internal/plugin-admin-app/tsconfig.json
diff --git a/plugins-bundled/internal/plugin-catalog-app/README.md b/plugins-bundled/internal/plugin-catalog-app/README.md
deleted file mode 100644
index b8a4e1f5540..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Grafana plugin catalog
-
-Allow Admin users to browse and manage plugins from within Grafana.
-
-This plugin is **included** with Grafana however it is only accessible if [enabled in Grafana settings](https://grafana.com/docs/grafana/next/administration/configuration/#catalog_app_enabled).
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/RootPage.tsx b/plugins-bundled/internal/plugin-catalog-app/src/RootPage.tsx
deleted file mode 100644
index c54561ad9d0..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/src/RootPage.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { AppRootProps } from '@grafana/data';
-import { pages } from 'pages';
-import React from 'react';
-
-export const MarketplaceRootPage = React.memo(function MarketplaceRootPage(props: AppRootProps) {
- const {
- path,
- query: { tab },
- } = props;
- // Required to support grafana instances that use a custom `root_url`.
- const pathWithoutLeadingSlash = path.replace(/^\//, '');
-
- const Page = pages.find(({ id }) => id === tab)?.component || pages[0].component;
- return ;
-});
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/hooks/useOrg.tsx b/plugins-bundled/internal/plugin-catalog-app/src/hooks/useOrg.tsx
deleted file mode 100644
index 7f4fcac5c82..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/src/hooks/useOrg.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useState, useEffect } from 'react';
-import { Org } from '../types';
-import { api } from '../api';
-
-interface State {
- isLoading: boolean;
- org?: Org;
-}
-
-export const useOrg = (slug: string): State => {
- const [state, setState] = useState({
- isLoading: true,
- });
-
- useEffect(() => {
- const fetchOrgData = async () => {
- const org = await api.getOrg(slug);
- setState({ org, isLoading: false });
- };
- fetchOrgData();
- }, [slug]);
-
- return state;
-};
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/img/browse.png b/plugins-bundled/internal/plugin-catalog-app/src/img/browse.png
deleted file mode 100644
index 82ed80bb12c..00000000000
Binary files a/plugins-bundled/internal/plugin-catalog-app/src/img/browse.png and /dev/null differ
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/img/details.png b/plugins-bundled/internal/plugin-catalog-app/src/img/details.png
deleted file mode 100644
index 15831b8a9f2..00000000000
Binary files a/plugins-bundled/internal/plugin-catalog-app/src/img/details.png and /dev/null differ
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/img/discover.png b/plugins-bundled/internal/plugin-catalog-app/src/img/discover.png
deleted file mode 100644
index 183a8e92f75..00000000000
Binary files a/plugins-bundled/internal/plugin-catalog-app/src/img/discover.png and /dev/null differ
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/module.ts b/plugins-bundled/internal/plugin-catalog-app/src/module.ts
deleted file mode 100644
index 37605d5484b..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/src/module.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { ComponentClass } from 'react';
-
-import { AppPlugin, AppRootProps } from '@grafana/data';
-import { MarketplaceRootPage } from './RootPage';
-
-export const plugin = new AppPlugin().setRootPage((MarketplaceRootPage as unknown) as ComponentClass);
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/pages/OrgDetails.tsx b/plugins-bundled/internal/plugin-catalog-app/src/pages/OrgDetails.tsx
deleted file mode 100644
index 648f8a3bea2..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/src/pages/OrgDetails.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react';
-import { css } from '@emotion/css';
-
-import { AppRootProps, GrafanaTheme2 } from '@grafana/data';
-
-import { PluginList } from '../components/PluginList';
-import { usePlugins } from '../hooks/usePlugins';
-import { useOrg } from '../hooks/useOrg';
-
-import { useStyles2 } from '@grafana/ui';
-import { Page } from 'components/Page';
-import { Loader } from 'components/Loader';
-
-export const OrgDetails = ({ query }: AppRootProps) => {
- const { orgSlug } = query;
-
- const orgData = useOrg(orgSlug);
- const { isLoading, items } = usePlugins();
- const styles = useStyles2(getStyles);
-
- const plugins = items.filter((plugin) => plugin.orgSlug === orgSlug);
-
- if (isLoading) {
- return ;
- }
-
- return (
-
-
-

-
{orgData.org?.name}
-
-
-
- );
-};
-
-const getStyles = (theme: GrafanaTheme2) => {
- return {
- header: css`
- align-items: center;
- display: flex;
- margin-bottom: ${theme.spacing(3)};
- margin-top: ${theme.spacing(3)};
- `,
- img: css`
- height: 64px;
- max-width: 64px;
- object-fit: cover;
- width: 100%;
- `,
- orgName: css`
- margin-left: ${theme.spacing(3)};
- `,
- };
-};
diff --git a/plugins-bundled/internal/plugin-catalog-app/src/plugin.json b/plugins-bundled/internal/plugin-catalog-app/src/plugin.json
deleted file mode 100644
index 625651b4db7..00000000000
--- a/plugins-bundled/internal/plugin-catalog-app/src/plugin.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
- "$schema": "https://github.com/grafana/grafana/raw/master/docs/sources/developers/plugins/plugin.schema.json",
- "type": "app",
- "name": "Plugin Catalog",
- "id": "grafana-plugin-catalog-app",
- "backend": false,
- "info": {
- "author": {
- "name": "Grafana Labs",
- "url": "https://grafana.com"
- },
- "keywords": ["plugins"],
- "logos": {
- "small": "img/logo.svg",
- "large": "img/logo.svg"
- },
- "links": [],
- "screenshots": [
- {
- "name": "Discover",
- "path": "img/discover.png"
- },
- {
- "name": "Browse",
- "path": "img/browse.png"
- },
- {
- "name": "Install",
- "path": "img/details.png"
- }
- ],
- "version": "%VERSION%",
- "updated": "%TODAY%"
- },
- "includes": [
- {
- "type": "page",
- "name": "Discover",
- "path": "/a/grafana-plugin-catalog-app?tab=discover",
- "role": "Admin",
- "addToNav": true,
- "defaultNav": true
- },
- {
- "type": "page",
- "name": "Browse",
- "path": "/a/grafana-plugin-catalog-app/?tab=browse",
- "role": "Admin",
- "addToNav": true
- },
- {
- "type": "page",
- "name": "Library",
- "path": "/a/grafana-plugin-catalog-app/?tab=library",
- "role": "Admin",
- "addToNav": true
- }
- ],
- "dependencies": {
- "grafanaDependency": ">=8.0.0",
- "grafanaVersion": "8.0.x",
- "plugins": []
- }
-}
diff --git a/public/app/features/datasources/state/buildCategories.ts b/public/app/features/datasources/state/buildCategories.ts
index 484cdfe1385..9cea0ee5914 100644
--- a/public/app/features/datasources/state/buildCategories.ts
+++ b/public/app/features/datasources/state/buildCategories.ts
@@ -203,7 +203,7 @@ function getPhantomPlugin(options: GetPhantomPluginOptions): DataSourcePluginMet
author: { name: 'Grafana Labs' },
links: [
{
- url: config.catalogUrl + options.id,
+ url: config.pluginCatalogURL + options.id,
name: 'Install now',
},
],
diff --git a/public/app/features/plugins/AppRootPage.tsx b/public/app/features/plugins/AppRootPage.tsx
index a4243995557..9c65fca1bcf 100644
--- a/public/app/features/plugins/AppRootPage.tsx
+++ b/public/app/features/plugins/AppRootPage.tsx
@@ -86,6 +86,7 @@ class AppRootPage extends Component {
}
onNavChanged = (nav: NavModel) => {
+ console.log('NAV CHANGED!!!', nav);
this.setState({ nav });
};
diff --git a/public/app/features/plugins/PluginListPage.tsx b/public/app/features/plugins/PluginListPage.tsx
index b0e6ab2b3c4..5f0f123abe0 100644
--- a/public/app/features/plugins/PluginListPage.tsx
+++ b/public/app/features/plugins/PluginListPage.tsx
@@ -12,6 +12,7 @@ import { setPluginsSearchQuery } from './state/reducers';
import { useAsync } from 'react-use';
import { selectors } from '@grafana/e2e-selectors';
import { PluginsErrorsInfo } from './PluginsErrorsInfo';
+import { config } from '@grafana/runtime';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'plugins'),
@@ -40,11 +41,18 @@ export const PluginListPage: React.FC = ({
loadPlugins();
}, [loadPlugins]);
+ let actionTarget: string | undefined = '_blank';
const linkButton = {
href: 'https://grafana.com/plugins?utm_source=grafana_plugin_list',
title: 'Find more plugins on Grafana.com',
};
+ if (config.pluginAdminEnabled) {
+ linkButton.href = '/a/grafana-plugin-admin-app/';
+ linkButton.title = 'Install & manage plugins';
+ actionTarget = undefined;
+ }
+
return (
@@ -54,7 +62,7 @@ export const PluginListPage: React.FC = ({
setSearchQuery={(query) => setPluginsSearchQuery(query)}
linkButton={linkButton}
placeholder="Search by name, author, description or type"
- target="_blank"
+ target={actionTarget}
/>