diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md
index 28404d6d7dd..2d8b19f3c98 100644
--- a/docs/sources/administration/provisioning.md
+++ b/docs/sources/administration/provisioning.md
@@ -154,7 +154,7 @@ Since not all datasources have the same configuration settings we only have the
| maxSeries | number | Influxdb | Max number of series/tables that Grafana processes |
| httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to POST |
| customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. |
-| manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI |
+| manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI |
| esVersion | string | Elasticsearch | Elasticsearch version (E.g. `7.0.0`, `7.6.1`) |
| timeField | string | Elasticsearch | Which field that should be used as timestamp |
| interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' |
diff --git a/docs/sources/auth/azuread.md b/docs/sources/auth/azuread.md
index b6c86bb1825..ed1e7aefbda 100644
--- a/docs/sources/auth/azuread.md
+++ b/docs/sources/auth/azuread.md
@@ -109,10 +109,12 @@ allowed_groups =
```
You can also use these environment variables to configure **client_id** and **client_secret**:
+
```
GF_AUTH_AZUREAD_CLIENT_ID
GF_AUTH_AZUREAD_CLIENT_SECRET
```
+
**Note:** Ensure that the [root_url]({{< relref "../administration/configuration/#root-url" >}}) in Grafana is set in your Azure Application Reply URLs (**App** -> **Settings** -> **Reply URLs**)
### Configure allowed groups
diff --git a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
index b828e9cef79..0a1b7d56b0e 100644
--- a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
+++ b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
@@ -31,8 +31,8 @@ The reference information that follows complements conceptual information about
## Default built-in role assignments
-| Built-in role | Associated role | Description |
-| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Built-in role | Associated role | Description |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Grafana Admin | `fixed:permissions:admin:edit` `fixed:permissions:admin:read` `fixed:provisioning:admin` `fixed:reporting:admin:edit` `fixed:reporting:admin:read` `fixed:users:admin:edit` `fixed:users:admin:read` `fixed:users:org:edit` `fixed:users:org:read` `fixed:ldap:admin:edit` `fixed:ldap:admin:read` `fixed:server:admin:read` `fixed:settings:admin:read` `fixed:settings:admin:edit` | Allow access to the same resources and permissions the [Grafana server administrator]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) has by default. |
-| Admin | `fixed:users:org:edit` `fixed:users:org:read` `fixed:reporting:admin:edit` `fixed:reporting:admin:read` | Allow access to the same resources and permissions that the [Grafana organization administrator]({{< relref "../../permissions/organization_roles.md" >}}) has by default. |
+| Admin | `fixed:users:org:edit` `fixed:users:org:read` `fixed:reporting:admin:edit` `fixed:reporting:admin:read` | Allow access to the same resources and permissions that the [Grafana organization administrator]({{< relref "../../permissions/organization_roles.md" >}}) has by default. |
| Editor | `fixed:datasource:editor:read` |
diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts
index c8a927c1921..202a579e73a 100644
--- a/packages/grafana-e2e-selectors/src/selectors/pages.ts
+++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts
@@ -159,6 +159,7 @@ export const Pages = {
PluginPage: {
page: 'Plugin page',
signatureInfo: 'Plugin signature info',
+ disabledInfo: 'Plugin disabled info',
},
PlaylistForm: {
name: 'Playlist name',
diff --git a/pkg/infra/process/root_check.go b/pkg/infra/process/root_check.go
index bcf58a346eb..20b2556736e 100644
--- a/pkg/infra/process/root_check.go
+++ b/pkg/infra/process/root_check.go
@@ -1,3 +1,4 @@
+//go:build !windows
// +build !windows
package process
diff --git a/pkg/infra/process/root_check_windows.go b/pkg/infra/process/root_check_windows.go
index 41a6c1e5aab..3b1eb3b0a9b 100644
--- a/pkg/infra/process/root_check_windows.go
+++ b/pkg/infra/process/root_check_windows.go
@@ -1,3 +1,4 @@
+//go:build windows
// +build windows
package process
diff --git a/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts b/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts
index af1678a85a0..5f16a962580 100644
--- a/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts
+++ b/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts
@@ -16,6 +16,7 @@ export default {
isDev: false,
isEnterprise: false,
isInstalled: false,
+ isDisabled: false,
name: 'Zabbix',
orgName: 'Alexander Zobnin',
popularity: 0.2093,
diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts
index 5eec02b8c92..59f96e07c67 100644
--- a/public/app/features/plugins/admin/api.ts
+++ b/public/app/features/plugins/admin/api.ts
@@ -1,6 +1,7 @@
import { getBackendSrv } from '@grafana/runtime';
import { API_ROOT, GRAFANA_API_ROOT } from './constants';
import { mergeLocalsAndRemotes, mergeLocalAndRemote } from './helpers';
+import { PluginError } from '@grafana/data';
import {
PluginDetails,
Org,
@@ -13,9 +14,13 @@ import {
} from './types';
export async function getCatalogPlugins(): Promise {
- const [localPlugins, remotePlugins] = await Promise.all([getLocalPlugins(), getRemotePlugins()]);
+ const [localPlugins, remotePlugins, pluginErrors] = await Promise.all([
+ getLocalPlugins(),
+ getRemotePlugins(),
+ getPluginErrors(),
+ ]);
- return mergeLocalsAndRemotes(localPlugins, remotePlugins);
+ return mergeLocalsAndRemotes(localPlugins, remotePlugins, pluginErrors);
}
export async function getCatalogPlugin(id: string): Promise {
@@ -68,6 +73,14 @@ async function getPlugin(slug: string): Promise {
};
}
+async function getPluginErrors(): Promise {
+ try {
+ return await getBackendSrv().get(`${API_ROOT}/errors`);
+ } catch (error) {
+ return [];
+ }
+}
+
async function getRemotePlugin(id: string, isInstalled: boolean): Promise {
try {
return await getBackendSrv().get(`${GRAFANA_API_ROOT}/plugins/${id}`);
diff --git a/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx
new file mode 100644
index 00000000000..22178e64fe4
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { PluginErrorCode } from '@grafana/data';
+import { Badge } from '@grafana/ui';
+
+type Props = { error?: PluginErrorCode };
+
+export function PluginDisabledBadge({ error }: Props): React.ReactElement {
+ const tooltip = errorCodeToTooltip(error);
+ return ;
+}
+
+function errorCodeToTooltip(error?: PluginErrorCode): string | undefined {
+ switch (error) {
+ case PluginErrorCode.modifiedSignature:
+ return 'Plugin disabled due to modified content';
+ case PluginErrorCode.invalidSignature:
+ return 'Plugin disabled due to invalid plugin signature';
+ case PluginErrorCode.missingSignature:
+ return 'Plugin disabled due to missing plugin signature';
+ default:
+ return `Plugin disabled due to unkown error: ${error}`;
+ }
+}
diff --git a/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx
new file mode 100644
index 00000000000..2843e4a75c4
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui';
+import { CatalogPlugin } from '../../types';
+import { getBadgeColor } from './sharedStyles';
+import { config } from '@grafana/runtime';
+
+type Props = { plugin: CatalogPlugin };
+
+export function PluginEnterpriseBadge({ plugin }: Props): React.ReactElement {
+ const customBadgeStyles = useStyles2(getBadgeColor);
+ const onClick = (ev: React.MouseEvent) => {
+ ev.preventDefault();
+ window.open(
+ `https://grafana.com/grafana/plugins/${plugin.id}?utm_source=grafana_catalog_learn_more`,
+ '_blank',
+ 'noopener,noreferrer'
+ );
+ };
+
+ if (config.licenseInfo?.hasValidLicense) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx
new file mode 100644
index 00000000000..dc035ec77ff
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { Badge, useStyles2 } from '@grafana/ui';
+import { getBadgeColor } from './sharedStyles';
+
+export function PluginInstalledBadge(): React.ReactElement {
+ const customBadgeStyles = useStyles2(getBadgeColor);
+ return ;
+}
diff --git a/public/app/features/plugins/admin/components/Badges/index.ts b/public/app/features/plugins/admin/components/Badges/index.ts
new file mode 100644
index 00000000000..12190d31a30
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/index.ts
@@ -0,0 +1,3 @@
+export { PluginDisabledBadge } from './PluginDisabledBadge';
+export { PluginInstalledBadge } from './PluginInstallBadge';
+export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
diff --git a/public/app/features/plugins/admin/components/Badges/sharedStyles.ts b/public/app/features/plugins/admin/components/Badges/sharedStyles.ts
new file mode 100644
index 00000000000..26486c6ad57
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/sharedStyles.ts
@@ -0,0 +1,8 @@
+import { css } from '@emotion/css';
+import { GrafanaTheme2 } from '@grafana/data';
+
+export const getBadgeColor = (theme: GrafanaTheme2) => css`
+ background: ${theme.colors.background.primary};
+ border-color: ${theme.colors.border.strong};
+ color: ${theme.colors.text.secondary};
+`;
diff --git a/public/app/features/plugins/admin/components/InstallControls/index.tsx b/public/app/features/plugins/admin/components/InstallControls/index.tsx
index 7aad502ee27..f0d3ae37e97 100644
--- a/public/app/features/plugins/admin/components/InstallControls/index.tsx
+++ b/public/app/features/plugins/admin/components/InstallControls/index.tsx
@@ -32,7 +32,7 @@ export const InstallControls = ({ plugin }: Props) => {
: PluginStatus.UNINSTALL
: PluginStatus.INSTALL;
- if (plugin.isCore) {
+ if (plugin.isCore || plugin.isDisabled) {
return null;
}
diff --git a/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
new file mode 100644
index 00000000000..88eadc6a23d
--- /dev/null
+++ b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { PluginErrorCode } from '@grafana/data';
+import { Alert } from '@grafana/ui';
+import { CatalogPlugin } from '../types';
+import { selectors } from '@grafana/e2e-selectors';
+
+type Props = {
+ className?: string;
+ plugin: CatalogPlugin;
+};
+
+export function PluginDetailsDisabledError({ className, plugin }: Props): React.ReactElement | null {
+ if (!plugin.isDisabled) {
+ return null;
+ }
+
+ return (
+
+ {renderDescriptionFromError(plugin.error)}
+
Please contact your server administrator to get this resolved.
+
+ Read more about managing plugins
+
+
+ );
+}
+
+function renderDescriptionFromError(error?: PluginErrorCode): React.ReactElement {
+ switch (error) {
+ case PluginErrorCode.modifiedSignature:
+ return (
+
+ Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
+ discovered that the content of this plugin does not match its signature. We can not guarantee the trustworthy
+ of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are
+ running a verified version of this plugin.
+
+ );
+ case PluginErrorCode.invalidSignature:
+ return (
+
+ Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
+ discovered that it was invalid. We can not guarantee the trustworthy of this plugin and have therefore
+ disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this
+ plugin.
+
+ );
+ case PluginErrorCode.missingSignature:
+ return (
+
+ Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
+ discovered that there is no signature for this plugin. We can not guarantee the trustworthy of this plugin and
+ have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified
+ version of this plugin.
+
+ );
+ default:
+ return (
+
+ We failed to run this plugin due to an unkown reason and have therefor disabled it. We recommend you to
+ reinstall the plugin to make sure you are running a working version of this plugin.
+