mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Display plugin permissions required (#78355)
* Add definition of external service registration * Add style and tables for permissions needed * Add external service registration to local without counterpart * Add feature toggle check * Add feature flag check in the backend as well * Add the disclaimer for permissions --------- Co-authored-by: Gabriel MABILLE <gabriel.mabille@grafana.com>
This commit is contained in:
parent
e27e2f66ba
commit
824e0f9ce8
@ -2,6 +2,7 @@ package dtos
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
)
|
||||
|
||||
@ -47,6 +48,7 @@ type PluginListItem struct {
|
||||
SignatureOrg string `json:"signatureOrg"`
|
||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
IAM *plugindef.IAM `json:"iam,omitempty"`
|
||||
}
|
||||
|
||||
type PluginList []PluginListItem
|
||||
|
@ -144,6 +144,10 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons
|
||||
AngularDetected: pluginDef.Angular.Detected,
|
||||
}
|
||||
|
||||
if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) {
|
||||
listItem.IAM = pluginDef.IAM
|
||||
}
|
||||
|
||||
update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), pluginDef.ID)
|
||||
if exists {
|
||||
listItem.LatestVersion = update
|
||||
|
@ -26,6 +26,7 @@ export async function getPluginDetails(id: string): Promise<CatalogPluginDetails
|
||||
readme: localReadme || remote?.readme,
|
||||
versions,
|
||||
statusContext: remote?.statusContext ?? '',
|
||||
iam: remote?.json?.iam,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { AppPlugin, GrafanaTheme2, PluginContextProvider, UrlQueryMap } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { CellProps, Column, InteractiveTable, Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { VersionList } from '../components/VersionList';
|
||||
import { usePluginConfig } from '../hooks/usePluginConfig';
|
||||
import { CatalogPlugin, PluginTabIds } from '../types';
|
||||
import { CatalogPlugin, Permission, PluginTabIds } from '../types';
|
||||
|
||||
import { AppConfigCtrlWrapper } from './AppConfigWrapper';
|
||||
import { PluginDashboards } from './PluginDashboards';
|
||||
@ -18,10 +19,28 @@ type Props = {
|
||||
pageId: string;
|
||||
};
|
||||
|
||||
type Cell<T extends keyof Permission = keyof Permission> = CellProps<Permission, Permission[T]>;
|
||||
|
||||
export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.Element {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { value: pluginConfig } = usePluginConfig(plugin);
|
||||
|
||||
const columns: Array<Column<Permission>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'action',
|
||||
header: 'Action',
|
||||
cell: ({ cell: { value } }: Cell<'action'>) => value,
|
||||
},
|
||||
{
|
||||
id: 'scope',
|
||||
header: 'Scope',
|
||||
cell: ({ cell: { value } }: Cell<'scope'>) => value,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
if (pageId === PluginTabIds.OVERVIEW) {
|
||||
return (
|
||||
<div
|
||||
@ -49,6 +68,31 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E
|
||||
);
|
||||
}
|
||||
|
||||
// Permissions will be returned in the iam field for installed plugins and in the details.iam field when fetching details from gcom
|
||||
const permissions = plugin.iam?.permissions || plugin.details?.iam?.permissions;
|
||||
|
||||
const displayPermissions =
|
||||
config.featureToggles.externalServiceAccounts &&
|
||||
pageId === PluginTabIds.IAM &&
|
||||
permissions &&
|
||||
permissions.length > 0;
|
||||
|
||||
if (displayPermissions) {
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row">
|
||||
The {plugin.name} plugin needs a service account to be able to query Grafana. The following list contains the
|
||||
permissions available to the service account:
|
||||
</Stack>
|
||||
<InteractiveTable
|
||||
columns={columns}
|
||||
data={permissions}
|
||||
getRowId={(permission: Permission) => String(permission.action)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginConfig?.configPages) {
|
||||
for (const configPage of pluginConfig.configPages) {
|
||||
if (pageId === configPage.id) {
|
||||
|
@ -161,6 +161,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
||||
accessControl: accessControl,
|
||||
angularDetected,
|
||||
isFullyInstalled: true,
|
||||
iam: plugin.iam,
|
||||
};
|
||||
}
|
||||
|
||||
@ -218,6 +219,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
|
||||
accessControl: local?.accessControl,
|
||||
angularDetected: local?.angularDetected || remote?.angularDetected,
|
||||
isFullyInstalled: Boolean(local) || isDisabled,
|
||||
iam: local?.iam,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,16 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabI
|
||||
return navModelChildren;
|
||||
}
|
||||
|
||||
if (config.featureToggles.externalServiceAccounts && (plugin?.iam || plugin?.details?.iam)) {
|
||||
navModelChildren.push({
|
||||
text: PluginTabLabels.IAM,
|
||||
icon: 'shield',
|
||||
id: PluginTabIds.IAM,
|
||||
url: `${pathname}?page=${PluginTabIds.IAM}`,
|
||||
active: PluginTabIds.IAM === currentPageId,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.featureToggles.panelTitleSearch && pluginConfig.meta.type === PluginType.panel) {
|
||||
navModelChildren.push({
|
||||
text: PluginTabLabels.USAGE,
|
||||
|
@ -62,6 +62,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
|
||||
// instance plugins may not be fully installed, which means a new instance
|
||||
// running the plugin didn't started yet
|
||||
isFullyInstalled?: boolean;
|
||||
iam?: IdentityAccessManagement;
|
||||
}
|
||||
|
||||
export interface CatalogPluginDetails {
|
||||
@ -74,6 +75,7 @@ export interface CatalogPluginDetails {
|
||||
grafanaDependency?: string;
|
||||
pluginDependencies?: PluginDependencies['plugins'];
|
||||
statusContext?: string;
|
||||
iam?: IdentityAccessManagement;
|
||||
}
|
||||
|
||||
export interface CatalogPluginInfo {
|
||||
@ -93,6 +95,7 @@ export type RemotePlugin = {
|
||||
internal: boolean;
|
||||
json?: {
|
||||
dependencies: PluginDependencies;
|
||||
iam?: IdentityAccessManagement;
|
||||
info: {
|
||||
links: Array<{
|
||||
name: string;
|
||||
@ -175,8 +178,18 @@ export type LocalPlugin = WithAccessControlMetadata & {
|
||||
type: PluginType;
|
||||
dependencies: PluginDependencies;
|
||||
angularDetected: boolean;
|
||||
iam?: IdentityAccessManagement;
|
||||
};
|
||||
|
||||
interface IdentityAccessManagement {
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
action: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
interface Rel {
|
||||
name: string;
|
||||
url: string;
|
||||
@ -231,6 +244,7 @@ export enum PluginTabLabels {
|
||||
CONFIG = 'Config',
|
||||
DASHBOARDS = 'Dashboards',
|
||||
USAGE = 'Usage',
|
||||
IAM = 'IAM',
|
||||
}
|
||||
|
||||
export enum PluginTabIds {
|
||||
@ -239,6 +253,7 @@ export enum PluginTabIds {
|
||||
CONFIG = 'config',
|
||||
DASHBOARDS = 'dashboards',
|
||||
USAGE = 'usage',
|
||||
IAM = 'iam',
|
||||
}
|
||||
|
||||
export enum RequestStatus {
|
||||
|
Loading…
Reference in New Issue
Block a user