Plugin Admin App: make the catalog look like internal component (#34341)
* Allow Route component usage in app plugins * i tried * fix catalog app * fix catalog app * fix catalog app * cleanup imports * plugin catalog enabled to plugin admin * rename plugin catalog to plugin admin * expose catalog url * update text * import from react-router-dom * fix imports -- add logging * merge changes * avoid onNavUpdate * Fixed onNavChange issues * fix library imports * more links Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Change Log
|
||||
|
||||
Changes are included in the grafana core changelog
|
||||
@@ -0,0 +1,3 @@
|
||||
# Grafana admin app
|
||||
|
||||
The grafana catalog is enabled or disabled by setting `plugin_admin_enabled` in the setup files.
|
||||
+3
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@grafana-plugins/catalog-app",
|
||||
"name": "@grafana-plugins/admin-app",
|
||||
"version": "8.1.0-pre",
|
||||
"description": "Plugins catalog",
|
||||
"description": "Plugins admin",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -18,9 +18,7 @@
|
||||
"@grafana/data": "8.1.0-pre",
|
||||
"@grafana/runtime": "8.1.0-pre",
|
||||
"@grafana/toolkit": "8.1.0-pre",
|
||||
"@grafana/ui": "8.1.0-pre",
|
||||
"@types/semver": "^7.3.4",
|
||||
"semver": "^7.3.4"
|
||||
"@grafana/ui": "8.1.0-pre"
|
||||
},
|
||||
"volta": {
|
||||
"node": "12.16.2"
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AppRootProps } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { Discover } from 'pages/Discover';
|
||||
import { Browse } from 'pages/Browse';
|
||||
import { PluginDetails } from 'pages/PluginDetails';
|
||||
import { Library } from 'pages/Library';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { NotEnabled } from 'pages/NotEnabed';
|
||||
|
||||
export const CatalogRootPage = React.memo(function CatalogRootPage(props: AppRootProps) {
|
||||
if (!config.pluginAdminEnabled) {
|
||||
return <NotEnabled {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Route
|
||||
exact
|
||||
path={`${props.basename}`}
|
||||
render={() => {
|
||||
return <Browse {...props} />; // or discover?
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${props.basename}/browse`}
|
||||
render={() => {
|
||||
return <Browse {...props} />;
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${props.basename}/discover`}
|
||||
render={() => {
|
||||
return <Discover {...props} />;
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={`${props.basename}/plugin/:pluginId`}
|
||||
render={() => {
|
||||
return <PluginDetails {...props} />;
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${props.basename}/library`}
|
||||
render={() => {
|
||||
return <Library {...props} />;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
+1
-1
@@ -35,7 +35,7 @@ export const Card = ({ href, text, image, layout = 'vertical' }: Props) => {
|
||||
|
||||
const getCardStyles = (theme: GrafanaTheme2) => ({
|
||||
root: css`
|
||||
background-color: ${theme.colors.background.primary};
|
||||
background-color: ${theme.colors.background.secondary};
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
+4
-5
@@ -17,10 +17,9 @@ import appEvents from 'grafana/app/core/app_events';
|
||||
interface Props {
|
||||
localPlugin?: Metadata;
|
||||
remotePlugin: Plugin;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export const InstallControls = ({ localPlugin, remotePlugin, slug }: Props) => {
|
||||
export const InstallControls = ({ localPlugin, remotePlugin }: Props) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isInstalled, setIsInstalled] = useState(Boolean(localPlugin));
|
||||
const [shouldUpdate, setShouldUpdate] = useState(
|
||||
@@ -32,7 +31,7 @@ export const InstallControls = ({ localPlugin, remotePlugin, slug }: Props) => {
|
||||
const onInstall = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.installPlugin(slug, remotePlugin.version);
|
||||
await api.installPlugin(remotePlugin.slug, remotePlugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Installed ${remotePlugin?.name}`]);
|
||||
setLoading(false);
|
||||
setIsInstalled(true);
|
||||
@@ -44,7 +43,7 @@ export const InstallControls = ({ localPlugin, remotePlugin, slug }: Props) => {
|
||||
const onUninstall = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.uninstallPlugin(slug);
|
||||
await api.uninstallPlugin(remotePlugin.slug);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${remotePlugin?.name}`]);
|
||||
setLoading(false);
|
||||
setIsInstalled(false);
|
||||
@@ -56,7 +55,7 @@ export const InstallControls = ({ localPlugin, remotePlugin, slug }: Props) => {
|
||||
const onUpdate = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.installPlugin(slug, remotePlugin.version);
|
||||
await api.installPlugin(remotePlugin.slug, remotePlugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Updated ${remotePlugin?.name}`]);
|
||||
setLoading(false);
|
||||
setShouldUpdate(false);
|
||||
+1
-1
@@ -23,7 +23,7 @@ export const PluginList = ({ plugins }: Props) => {
|
||||
return (
|
||||
<Card
|
||||
key={`${orgName}-${name}-${typeCode}`}
|
||||
href={`${PLUGIN_ROOT}?tab=plugin&slug=${slug}`}
|
||||
href={`${PLUGIN_ROOT}/plugin/${slug}`}
|
||||
image={
|
||||
<img
|
||||
src={`https://grafana.com/api/plugins/${slug}/versions/${version}/logos/small`}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { PluginConfigPageProps, AppPluginMeta } from '@grafana/data';
|
||||
import { LinkButton } from '@grafana/ui';
|
||||
import { PLUGIN_ROOT } from '../constants';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
interface Props extends PluginConfigPageProps<AppPluginMeta> {}
|
||||
|
||||
export const Settings = ({ plugin }: Props) => {
|
||||
if (!config.pluginAdminEnabled) {
|
||||
return <div>Plugin admin is not enabled.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LinkButton href={PLUGIN_ROOT}>Manage plugins</LinkButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const API_ROOT = '/api/plugins';
|
||||
export const PLUGIN_ID = 'grafana-plugin-catalog-app';
|
||||
export const PLUGIN_ID = 'grafana-plugin-admin-app';
|
||||
export const PLUGIN_ROOT = '/a/' + PLUGIN_ID;
|
||||
export const GRAFANA_API_ROOT = '/api/gnet';
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,10 @@
|
||||
import { AppPlugin } from '@grafana/data';
|
||||
import { Settings } from './config/Settings';
|
||||
import { CatalogRootPage } from './RootPage';
|
||||
|
||||
export const plugin = new AppPlugin().setRootPage(CatalogRootPage as any).addConfigPage({
|
||||
title: 'Settings',
|
||||
icon: 'info-circle',
|
||||
body: Settings as any,
|
||||
id: 'settings',
|
||||
});
|
||||
+15
-3
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { AppRootProps, SelectableValue, dateTimeParse } from '@grafana/data';
|
||||
import { Field, LoadingPlaceholder, Select } from '@grafana/ui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { locationSearchToObject } from '@grafana/runtime';
|
||||
|
||||
import { PluginList } from '../components/PluginList';
|
||||
import { SearchField } from '../components/SearchField';
|
||||
@@ -10,13 +12,23 @@ import { usePlugins } from '../hooks/usePlugins';
|
||||
import { useHistory } from '../hooks/useHistory';
|
||||
import { Plugin } from '../types';
|
||||
import { Page } from 'components/Page';
|
||||
import { CatalogTab, getCatalogNavModel } from './nav';
|
||||
|
||||
export const Browse = ({ query }: AppRootProps) => {
|
||||
const { q, filterBy, sortBy } = query;
|
||||
export const Browse = ({ meta, onNavChanged, basename }: AppRootProps) => {
|
||||
const location = useLocation();
|
||||
const query = locationSearchToObject(location.search);
|
||||
|
||||
const q = query.q as string;
|
||||
const filterBy = query.filterBy as string;
|
||||
const sortBy = query.sortBy as string;
|
||||
|
||||
const plugins = usePlugins();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
onNavChanged(getCatalogNavModel(CatalogTab.Browse, basename));
|
||||
}, [onNavChanged, basename]);
|
||||
|
||||
const onSortByChange = (value: SelectableValue<string>) => {
|
||||
history.push({ query: { sortBy: value.value } });
|
||||
};
|
||||
+12
-10
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { cx, css } from '@emotion/css';
|
||||
|
||||
import { dateTimeParse, GrafanaTheme2 } from '@grafana/data';
|
||||
import { dateTimeParse, AppRootProps, GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2, Legend, LinkButton } from '@grafana/ui';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
|
||||
import { PLUGIN_ROOT } from '../constants';
|
||||
import { Card } from '../components/Card';
|
||||
@@ -11,18 +12,19 @@ import { PluginList } from '../components/PluginList';
|
||||
import { SearchField } from '../components/SearchField';
|
||||
import { PluginTypeIcon } from '../components/PluginTypeIcon';
|
||||
import { usePlugins } from '../hooks/usePlugins';
|
||||
import { useHistory } from '../hooks/useHistory';
|
||||
import { Plugin } from '../types';
|
||||
import { Page } from 'components/Page';
|
||||
import { Loader } from 'components/Loader';
|
||||
|
||||
export const Discover = () => {
|
||||
export const Discover = ({ meta }: AppRootProps) => {
|
||||
const { items, isLoading } = usePlugins();
|
||||
const history = useHistory();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onSearch = (q: string) => {
|
||||
history.push({ query: { q, tab: 'browse' } });
|
||||
locationService.push({
|
||||
pathname: `${PLUGIN_ROOT}/browse`,
|
||||
search: `?q=${q}`,
|
||||
});
|
||||
};
|
||||
|
||||
const featuredPlugins = items.filter((_) => _.featured > 0);
|
||||
@@ -56,14 +58,14 @@ export const Discover = () => {
|
||||
{/* Most popular */}
|
||||
<div className={styles.legendContainer}>
|
||||
<Legend className={styles.legend}>Most popular</Legend>
|
||||
<LinkButton href={`${PLUGIN_ROOT}?tab=browse&sortBy=popularity`}>See more</LinkButton>
|
||||
<LinkButton href={`${PLUGIN_ROOT}/browse?sortBy=popularity`}>See more</LinkButton>
|
||||
</div>
|
||||
<PluginList plugins={mostPopular.slice(0, 5)} />
|
||||
|
||||
{/* Recently added */}
|
||||
<div className={styles.legendContainer}>
|
||||
<Legend className={styles.legend}>Recently added</Legend>
|
||||
<LinkButton href={`${PLUGIN_ROOT}?tab=browse&sortBy=published'`}>See more</LinkButton>
|
||||
<LinkButton href={`${PLUGIN_ROOT}/browse?sortBy=published'`}>See more</LinkButton>
|
||||
</div>
|
||||
<PluginList plugins={recentlyAdded.slice(0, 5)} />
|
||||
|
||||
@@ -72,19 +74,19 @@ export const Discover = () => {
|
||||
<Grid>
|
||||
<Card
|
||||
layout="horizontal"
|
||||
href={`${PLUGIN_ROOT}?tab=browse&filterBy=panel`}
|
||||
href={`${PLUGIN_ROOT}/browse?filterBy=panel`}
|
||||
image={<PluginTypeIcon typeCode="panel" size={18} />}
|
||||
text={<span className={styles.typeLegend}> Panels</span>}
|
||||
/>
|
||||
<Card
|
||||
layout="horizontal"
|
||||
href={`${PLUGIN_ROOT}?tab=browse&filterBy=datasource`}
|
||||
href={`${PLUGIN_ROOT}/browse?filterBy=datasource`}
|
||||
image={<PluginTypeIcon typeCode="datasource" size={18} />}
|
||||
text={<span className={styles.typeLegend}> Data sources</span>}
|
||||
/>
|
||||
<Card
|
||||
layout="horizontal"
|
||||
href={`${PLUGIN_ROOT}?tab=browse&filterBy=app`}
|
||||
href={`${PLUGIN_ROOT}/browse?filterBy=app`}
|
||||
image={<PluginTypeIcon typeCode="app" size={18} />}
|
||||
text={<span className={styles.typeLegend}> Apps</span>}
|
||||
/>
|
||||
+9
-4
@@ -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 = () => {
|
||||
) : (
|
||||
<p>
|
||||
You haven't installed any plugins. Browse the{' '}
|
||||
<a className={styles.link} href={`${PLUGIN_ROOT}/?tab=browse&sortBy=popularity`}>
|
||||
<a className={styles.link} href={`${PLUGIN_ROOT}/browse?sortBy=popularity`}>
|
||||
catalog
|
||||
</a>{' '}
|
||||
for plugins to install.
|
||||
@@ -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 (
|
||||
<Page>
|
||||
To enabled installing plugins, set the{' '}
|
||||
<a href="https://grafana.com/docs/grafana/latest/plugins/catalog">Plugin Catalog</a> instructions
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
+14
-7
@@ -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 <Loader />;
|
||||
}
|
||||
@@ -34,7 +41,7 @@ export const PluginDetails = ({ query }: AppRootProps) => {
|
||||
<Page>
|
||||
<div className={styles.headerContainer}>
|
||||
<img
|
||||
src={`${GRAFANA_API_ROOT}/plugins/${slug}/versions/${remote?.version}/logos/small`}
|
||||
src={`${GRAFANA_API_ROOT}/plugins/${pluginId}/versions/${remote?.version}/logos/small`}
|
||||
className={css`
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
@@ -45,7 +52,7 @@ export const PluginDetails = ({ query }: AppRootProps) => {
|
||||
<div className={styles.headerWrapper}>
|
||||
<h1>{remote?.name}</h1>
|
||||
<div className={styles.headerLinks}>
|
||||
<a className={styles.headerOrgName} href={`${PLUGIN_ROOT}?tab=org&orgSlug=${remote?.orgSlug}`}>
|
||||
<a className={styles.headerOrgName} href={`${PLUGIN_ROOT}`}>
|
||||
{remote?.orgName}
|
||||
</a>
|
||||
{links.map((link: any) => (
|
||||
@@ -62,7 +69,7 @@ export const PluginDetails = ({ query }: AppRootProps) => {
|
||||
{version && <span>{version}</span>}
|
||||
</div>
|
||||
<p>{description}</p>
|
||||
{remote && <InstallControls localPlugin={local} remotePlugin={remote} slug={slug} />}
|
||||
{remote && <InstallControls localPlugin={local} remotePlugin={remote} />}
|
||||
</div>
|
||||
</div>
|
||||
<TabsBar>
|
||||
-7
@@ -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',
|
||||
},
|
||||
];
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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 <Page {...props} path={pathWithoutLeadingSlash} />;
|
||||
});
|
||||
@@ -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<State>({
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrgData = async () => {
|
||||
const org = await api.getOrg(slug);
|
||||
setState({ org, isLoading: false });
|
||||
};
|
||||
fetchOrgData();
|
||||
}, [slug]);
|
||||
|
||||
return state;
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 529 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 396 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 509 KiB |
@@ -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<AppRootProps>);
|
||||
@@ -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 <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className={styles.header}>
|
||||
<img src={orgData.org?.avatarUrl} className={styles.img} />
|
||||
<h1 className={styles.orgName}>{orgData.org?.name}</h1>
|
||||
</div>
|
||||
<PluginList plugins={plugins} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
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)};
|
||||
`,
|
||||
};
|
||||
};
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user