Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a01197f27 | |||
| 1d3f09d519 | |||
| ec1ace398e | |||
| fe5aa3e281 | |||
| a01777eafa |
@@ -35,10 +35,10 @@ For Grafana Cloud users, Grafana Support is not authorised to make org role chan
|
||||
|
||||
## Grafana server administrators
|
||||
|
||||
A Grafana server administrator manages server-wide settings and access to resources such as organizations, users, and licenses. Grafana includes a default server administrator that you can use to manage all of Grafana, or you can divide that responsibility among other server administrators that you create.
|
||||
A Grafana server administrator (sometimes referred to as a **Grafana Admin**) manages server-wide settings and access to resources such as organizations, users, and licenses. Grafana includes a default server administrator that you can use to manage all of Grafana, or you can divide that responsibility among other server administrators that you create.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
The server administrator role does not mean that the user is also a Grafana [organization administrator](#organization-roles).
|
||||
{{< admonition type="caution" >}}
|
||||
The server administrator role is distinct from the [organization administrator](#organization-roles) role.
|
||||
{{< /admonition >}}
|
||||
|
||||
A server administrator can perform the following tasks:
|
||||
@@ -50,7 +50,7 @@ A server administrator can perform the following tasks:
|
||||
- Upgrade the server to Grafana Enterprise.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
The server administrator role does not exist in Grafana Cloud.
|
||||
The server administrator (Grafana Admin) role does not exist in Grafana Cloud.
|
||||
{{< /admonition >}}
|
||||
|
||||
To assign or remove server administrator privileges, see [Server user management](../user-management/server-user-management/assign-remove-server-admin-privileges/).
|
||||
|
||||
+102
-59
@@ -53,6 +53,11 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/custom-role-actions-scopes/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/account-management/authentication-and-permissions/access-control/custom-role-actions-scopes/
|
||||
rbac-terraform-provisioning:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/rbac-terraform-provisioning/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/account-management/authentication-and-permissions/access-control/rbac-terraform-provisioning/
|
||||
rbac-grafana-provisioning:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/
|
||||
@@ -145,7 +150,13 @@ Refer to the [RBAC HTTP API](ref:api-rbac-get-a-role) for more details.
|
||||
|
||||
## Create custom roles
|
||||
|
||||
This section shows you how to create a custom RBAC role using Grafana provisioning and the HTTP API.
|
||||
This section shows you how to create a custom RBAC role using Grafana provisioning or the HTTP API.
|
||||
|
||||
Creating and editing custom roles is not currently possible in the Grafana UI. To manage custom roles, use one of the following methods:
|
||||
|
||||
- [Provisioning](ref:rbac-grafana-provisioning) (for self-managed instances)
|
||||
- [HTTP API](ref:api-rbac-create-a-new-custom-role)
|
||||
- [Terraform](ref:rbac-terraform-provisioning)
|
||||
|
||||
Create a custom role when basic roles and fixed roles do not meet your permissions requirements.
|
||||
|
||||
@@ -153,14 +164,101 @@ Create a custom role when basic roles and fixed roles do not meet your permissio
|
||||
|
||||
- [Plan your RBAC rollout strategy](ref:plan-rbac-rollout-strategy).
|
||||
- Determine which permissions you want to add to the custom role. To see a list of actions and scope, refer to [RBAC permissions, actions, and scopes](ref:custom-role-actions-scopes).
|
||||
- [Enable role provisioning](ref:rbac-grafana-provisioning).
|
||||
- Ensure that you have permissions to create a custom role.
|
||||
- By default, the Grafana Admin role has permission to create custom roles.
|
||||
- A Grafana Admin can delegate the custom role privilege to another user by creating a custom role with the relevant permissions and adding the `permissions:type:delegate` scope.
|
||||
|
||||
### Create custom roles using provisioning
|
||||
### Create custom roles using the HTTP API
|
||||
|
||||
[File-based provisioning](ref:rbac-grafana-provisioning) is one method you can use to create custom roles.
|
||||
The following examples show you how to create a custom role using the Grafana HTTP API. For more information about the HTTP API, refer to [Create a new custom role](ref:api-rbac-create-a-new-custom-role).
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
When you create a custom role you can only give it the same permissions you already have. For example, if you only have `users:create` permissions, then you can't create a role that includes other permissions.
|
||||
{{< /admonition >}}
|
||||
|
||||
The following example creates a `custom:users:admin` role and assigns the `users:create` action to it.
|
||||
|
||||
**Example request**
|
||||
|
||||
```
|
||||
curl --location --request POST '<grafana_url>/api/access-control/roles/' \
|
||||
--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"version": 1,
|
||||
"uid": "jZrmlLCkGksdka",
|
||||
"name": "custom:users:admin",
|
||||
"displayName": "custom users admin",
|
||||
"description": "My custom role which gives users permissions to create users",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
{
|
||||
"action": "users:create"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Example response**
|
||||
|
||||
```
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "jZrmlLCkGksdka",
|
||||
"name": "custom:users:admin",
|
||||
"displayName": "custom users admin",
|
||||
"description": "My custom role which gives users permissions to create users",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
{
|
||||
"action": "users:create"
|
||||
"updated": "2021-05-17T22:07:31.569936+02:00",
|
||||
"created": "2021-05-17T22:07:31.569935+02:00"
|
||||
}
|
||||
],
|
||||
"updated": "2021-05-17T22:07:31.564403+02:00",
|
||||
"created": "2021-05-17T22:07:31.564403+02:00"
|
||||
}
|
||||
```
|
||||
|
||||
Refer to the [RBAC HTTP API](ref:api-rbac-create-a-new-custom-role) for more details.
|
||||
|
||||
### Create custom roles using Terraform
|
||||
|
||||
You can use the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs) to manage custom roles and their assignments. This is the recommended method for Grafana Cloud users who want to manage RBAC as code. For more information, refer to [Provisioning RBAC with Terraform](ref:rbac-terraform-provisioning).
|
||||
|
||||
The following example creates a custom role and assigns it to a team:
|
||||
|
||||
```terraform
|
||||
resource "grafana_role" "custom_folder_manager" {
|
||||
name = "custom:folders:manager"
|
||||
description = "Custom role for reading and creating folders"
|
||||
uid = "custom-folders-manager"
|
||||
version = 1
|
||||
global = true
|
||||
|
||||
permissions {
|
||||
action = "folders:read"
|
||||
scope = "folders:*"
|
||||
}
|
||||
|
||||
permissions {
|
||||
action = "folders:create"
|
||||
scope = "folders:uid:general" # Allows creating folders at the root level
|
||||
}
|
||||
}
|
||||
|
||||
resource "grafana_role_assignment" "custom_folder_manager_assignment" {
|
||||
role_uid = grafana_role.custom_folder_manager.uid
|
||||
teams = ["<TEAM_UID>"]
|
||||
}
|
||||
```
|
||||
|
||||
For more information, refer to the [`grafana_role`](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/role) and [`grafana_role_assignment`](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/role_assignment) documentation in the Terraform Registry.
|
||||
|
||||
### Create custom roles using file-based provisioning
|
||||
|
||||
You can use [file-based provisioning](ref:rbac-grafana-provisioning) to create custom roles for self-managed instances.
|
||||
|
||||
1. Open the YAML configuration file and locate the `roles` section.
|
||||
|
||||
@@ -251,61 +349,6 @@ roles:
|
||||
state: 'absent'
|
||||
```
|
||||
|
||||
### Create custom roles using the HTTP API
|
||||
|
||||
The following examples show you how to create a custom role using the Grafana HTTP API. For more information about the HTTP API, refer to [Create a new custom role](ref:api-rbac-create-a-new-custom-role).
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
You cannot create a custom role with permissions that you do not have. For example, if you only have `users:create` permissions, then you cannot create a role that includes other permissions.
|
||||
{{< /admonition >}}
|
||||
|
||||
The following example creates a `custom:users:admin` role and assigns the `users:create` action to it.
|
||||
|
||||
**Example request**
|
||||
|
||||
```
|
||||
curl --location --request POST '<grafana_url>/api/access-control/roles/' \
|
||||
--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"version": 1,
|
||||
"uid": "jZrmlLCkGksdka",
|
||||
"name": "custom:users:admin",
|
||||
"displayName": "custom users admin",
|
||||
"description": "My custom role which gives users permissions to create users",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
{
|
||||
"action": "users:create"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Example response**
|
||||
|
||||
```
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "jZrmlLCkGksdka",
|
||||
"name": "custom:users:admin",
|
||||
"displayName": "custom users admin",
|
||||
"description": "My custom role which gives users permissions to create users",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
{
|
||||
"action": "users:create"
|
||||
"updated": "2021-05-17T22:07:31.569936+02:00",
|
||||
"created": "2021-05-17T22:07:31.569935+02:00"
|
||||
}
|
||||
],
|
||||
"updated": "2021-05-17T22:07:31.564403+02:00",
|
||||
"created": "2021-05-17T22:07:31.564403+02:00"
|
||||
}
|
||||
```
|
||||
|
||||
Refer to the [RBAC HTTP API](ref:api-rbac-create-a-new-custom-role) for more details.
|
||||
|
||||
## Update basic role permissions
|
||||
|
||||
If the default basic role definitions do not meet your requirements, you can change their permissions.
|
||||
|
||||
+3
-2
@@ -6,7 +6,6 @@ description: Learn about RBAC Grafana provisioning and view an example YAML prov
|
||||
file that configures Grafana role assignments.
|
||||
labels:
|
||||
products:
|
||||
- cloud
|
||||
- enterprise
|
||||
menuTitle: Provisioning RBAC with Grafana
|
||||
title: Provisioning RBAC with Grafana
|
||||
@@ -52,11 +51,13 @@ refs:
|
||||
# Provisioning RBAC with Grafana
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Available in [Grafana Enterprise](/docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud).
|
||||
Available in [Grafana Enterprise](/docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/) for self-managed instances. This feature is not available in Grafana Cloud.
|
||||
{{< /admonition >}}
|
||||
|
||||
You can create, change or remove [Custom roles](ref:manage-rbac-roles-create-custom-roles-using-provisioning) and create or remove [basic role assignments](ref:assign-rbac-roles-assign-a-fixed-role-to-a-basic-role-using-provisioning), by adding one or more YAML configuration files in the `provisioning/access-control/` directory.
|
||||
|
||||
Because this method requires access to the file system where Grafana is running, it's only available for self-managed Grafana instances. To provision RBAC in Grafana Cloud, use [Terraform](ref:rbac-terraform-provisioning) or the [HTTP API](ref:api-rbac-create-and-manage-custom-roles).
|
||||
|
||||
Grafana performs provisioning during startup. After you make a change to the configuration file, you can reload it during runtime. You do not need to restart the Grafana server for your changes to take effect.
|
||||
|
||||
**Before you begin:**
|
||||
|
||||
@@ -984,6 +984,11 @@ export interface FeatureToggles {
|
||||
*/
|
||||
recentlyViewedDashboards?: boolean;
|
||||
/**
|
||||
* A/A test for recently viewed dashboards feature
|
||||
* @default false
|
||||
*/
|
||||
experimentRecentlyViewedDashboards?: boolean;
|
||||
/**
|
||||
* Enable configuration of alert enrichments in Grafana Cloud.
|
||||
* @default false
|
||||
*/
|
||||
|
||||
@@ -1625,6 +1625,15 @@ var (
|
||||
FrontendOnly: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "experimentRecentlyViewedDashboards",
|
||||
Description: "A/A test for recently viewed dashboards feature",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaFrontendSearchNavOrganise,
|
||||
FrontendOnly: true,
|
||||
HideFromDocs: true,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "alertEnrichment",
|
||||
Description: "Enable configuration of alert enrichments in Grafana Cloud.",
|
||||
|
||||
Generated
+1
@@ -223,6 +223,7 @@ kubernetesAuthnMutation,experimental,@grafana/identity-access-team,false,false,f
|
||||
kubernetesExternalGroupMapping,experimental,@grafana/identity-access-team,false,false,false
|
||||
restoreDashboards,experimental,@grafana/grafana-search-navigate-organise,false,false,false
|
||||
recentlyViewedDashboards,experimental,@grafana/grafana-search-navigate-organise,false,false,true
|
||||
experimentRecentlyViewedDashboards,experimental,@grafana/grafana-search-navigate-organise,false,false,true
|
||||
alertEnrichment,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertEnrichmentMultiStep,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertEnrichmentConditional,experimental,@grafana/alerting-squad,false,false,false
|
||||
|
||||
|
+15
@@ -1365,6 +1365,21 @@
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "experimentRecentlyViewedDashboards",
|
||||
"resourceVersion": "1768214542023",
|
||||
"creationTimestamp": "2026-01-12T10:42:22Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "A/A test for recently viewed dashboards feature",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/grafana-search-navigate-organise",
|
||||
"frontend": true,
|
||||
"hideFromDocs": true,
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "exploreLogsAggregatedMetrics",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memo, useEffect, useMemo } from 'react';
|
||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom-v5-compat';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { evaluateBooleanFlag } from '@grafana/runtime/internal';
|
||||
import { LinkButton, FilterInput, useStyles2, Text, Stack } from '@grafana/ui';
|
||||
import { useGetFolderQueryFacade, useUpdateFolder } from 'app/api/clients/folder/v1beta1/hooks';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
@@ -44,6 +45,7 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record<string
|
||||
const location = useLocation();
|
||||
const search = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
||||
const { isReadOnlyRepo, repoType } = useGetResourceRepositoryView({ folderName: folderUID });
|
||||
const isRecentlyViewedEnabled = !folderUID && evaluateBooleanFlag('recentlyViewedDashboards', false);
|
||||
|
||||
useEffect(() => {
|
||||
stateManager.initStateFromUrl(folderUID);
|
||||
@@ -73,6 +75,23 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record<string
|
||||
}
|
||||
}, [isSearching, searchState.result, stateManager]);
|
||||
|
||||
// Emit exposure event for A/A test once when page loads
|
||||
const hasEmittedExposureEvent = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRecentlyViewedEnabled || hasEmittedExposureEvent.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasEmittedExposureEvent.current = true;
|
||||
const isExperimentTreatment = evaluateBooleanFlag('experimentRecentlyViewedDashboards', false);
|
||||
|
||||
reportInteraction('dashboards_browse_list_viewed', {
|
||||
experiment_dashboard_list_recently_viewed: isExperimentTreatment ? 'treatment' : 'control',
|
||||
has_recently_viewed_component: isExperimentTreatment,
|
||||
});
|
||||
}, [isRecentlyViewedEnabled]);
|
||||
|
||||
const { data: folderDTO } = useGetFolderQueryFacade(folderUID);
|
||||
const [saveFolder] = useUpdateFolder();
|
||||
const navModel = useMemo(() => {
|
||||
@@ -179,8 +198,8 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record<string
|
||||
>
|
||||
<Page.Contents className={styles.pageContents}>
|
||||
<ProvisionedFolderPreviewBanner queryParams={queryParams} />
|
||||
{/* only show recently viewed dashboards when in root */}
|
||||
{!folderUID && <RecentlyViewedDashboards />}
|
||||
{/* only show recently viewed dashboards when in root and flag is enabled */}
|
||||
{isRecentlyViewedEnabled && <RecentlyViewedDashboards />}
|
||||
<div>
|
||||
<FilterInput
|
||||
placeholder={getSearchPlaceholder(searchState.includePanels)}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useAsyncRetry } from 'react-use';
|
||||
import { GrafanaTheme2, store } from '@grafana/data';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { evaluateBooleanFlag } from '@grafana/runtime/internal';
|
||||
import { Button, CollapsableSection, Spinner, Stack, Text, useStyles2, Grid } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo';
|
||||
@@ -28,9 +27,6 @@ export function RecentlyViewedDashboards() {
|
||||
retry,
|
||||
error,
|
||||
} = useAsyncRetry(async () => {
|
||||
if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) {
|
||||
return [];
|
||||
}
|
||||
return getRecentlyViewedDashboards(MAX_RECENT);
|
||||
}, []);
|
||||
const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0);
|
||||
@@ -48,7 +44,7 @@ export function RecentlyViewedDashboards() {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
if (!evaluateBooleanFlag('recentlyViewedDashboards', false) || recentDashboards.length === 0) {
|
||||
if (recentDashboards.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -123,6 +119,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
color: 'transparent',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
padding: 0,
|
||||
}),
|
||||
content: css({
|
||||
paddingTop: theme.spacing(0),
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@ describe('isDashboardDatasource', () => {
|
||||
{ refId: 'C', datasource: { uid: 'mysql-uid', type: 'mysql' } },
|
||||
];
|
||||
|
||||
const backendQueries = queries.filter((q) => !isDashboardDatasource(q));
|
||||
const nonDashboardQueries = queries.filter((q) => !isDashboardDatasource(q));
|
||||
|
||||
expect(backendQueries.map((q) => q.refId)).toEqual(['A', 'C']);
|
||||
expect(nonDashboardQueries.map((q) => q.refId)).toEqual(['A', 'C']);
|
||||
});
|
||||
|
||||
it('returns true when query has dashboard datasource uid', () => {
|
||||
|
||||
@@ -67,9 +67,9 @@ export function useSQLSchemas({ queries, enabled, timeRange }: UseSQLSchemasOpti
|
||||
|
||||
try {
|
||||
// Filter out Dashboard datasource queries - they are frontend-only and can't be processed by backend
|
||||
const backendQueries = currentQueries.filter((q) => !isDashboardDatasource(q));
|
||||
const nonDashboardQueries = currentQueries.filter((q) => !isDashboardDatasource(q));
|
||||
|
||||
if (backendQueries.length === 0) {
|
||||
if (nonDashboardQueries.length === 0) {
|
||||
setSchemas({ kind: 'SQLSchemaResponse', apiVersion: 'query.grafana.app/v0alpha1', sqlSchemas: {} });
|
||||
setLoading(false);
|
||||
return;
|
||||
@@ -81,7 +81,7 @@ export function useSQLSchemas({ queries, enabled, timeRange }: UseSQLSchemasOpti
|
||||
const response = await getBackendSrv().post<SQLSchemasResponse>(
|
||||
`/apis/query.grafana.app/v0alpha1/namespaces/${namespace}/sqlschemas/name`,
|
||||
{
|
||||
queries: backendQueries,
|
||||
queries: nonDashboardQueries,
|
||||
from: currentTimeRange.from.toISOString(),
|
||||
to: currentTimeRange.to.toISOString(),
|
||||
}
|
||||
|
||||
-5
@@ -36,9 +36,4 @@ describe('ConfigEditor', () => {
|
||||
expect(screen.getByTestId('url-auth-section')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('db-connection-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the informational alert', () => {
|
||||
render(<ConfigEditor {...defaultProps} />);
|
||||
expect(screen.getByText(/You are viewing a new design/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+1
-18
@@ -2,13 +2,12 @@ import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, Box, Stack, TextLink, Text, useStyles2 } from '@grafana/ui';
|
||||
import { Box, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { DatabaseConnectionSection } from './DatabaseConnectionSection';
|
||||
import { LeftSideBar } from './LeftSideBar';
|
||||
import { UrlAndAuthenticationSection } from './UrlAndAuthenticationSection';
|
||||
import { CONTAINER_MIN_WIDTH } from './constants';
|
||||
import { trackInfluxDBConfigV2FeedbackButtonClicked } from './tracking';
|
||||
import { Props } from './types';
|
||||
|
||||
export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }: Props) => {
|
||||
@@ -22,22 +21,6 @@ export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }: Prop
|
||||
</div>
|
||||
<Box width="60%" flex="1 1 auto" minWidth={CONTAINER_MIN_WIDTH}>
|
||||
<Stack direction="column">
|
||||
<Alert
|
||||
severity="info"
|
||||
title="You are viewing a new design for the InfluxDB configuration settings."
|
||||
className={styles.alertHeight}
|
||||
>
|
||||
<>
|
||||
<TextLink
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLSdi-zyX3c51vh937UKhNYYxhljUnFi6dQSlZv50mES9NrK-ig/viewform"
|
||||
external
|
||||
onClick={trackInfluxDBConfigV2FeedbackButtonClicked}
|
||||
>
|
||||
Share your thoughts
|
||||
</TextLink>{' '}
|
||||
to help us make it even better.
|
||||
</>
|
||||
</Alert>
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
Fields marked with * are required
|
||||
</Text>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { truncate } from 'lodash';
|
||||
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Box, Card, Icon, Link, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { LocationInfo } from 'app/features/search/service/types';
|
||||
@@ -25,6 +27,7 @@ export function DashListItem({
|
||||
onStarChange,
|
||||
}: Props) {
|
||||
const css = useStyles2(getStyles);
|
||||
const shortTitle = truncate(dashboard.name, { length: 40, omission: '…' });
|
||||
|
||||
const onCardLinkClick = () => {
|
||||
reportInteraction('grafana_recently_viewed_dashboards_click_card', {
|
||||
@@ -54,27 +57,35 @@ export function DashListItem({
|
||||
</div>
|
||||
) : (
|
||||
<Card className={css.dashlistCard} noMargin>
|
||||
<Stack justifyContent="space-between" alignItems="center">
|
||||
<Link href={url} onClick={onCardLinkClick}>
|
||||
{dashboard.name}
|
||||
</Link>
|
||||
<StarToolbarButton
|
||||
title={dashboard.name}
|
||||
group="dashboard.grafana.app"
|
||||
kind="Dashboard"
|
||||
id={dashboard.uid}
|
||||
onStarChange={onStarChange}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{showFolderNames && locationInfo && (
|
||||
<Stack alignItems="center" direction="row" gap={0}>
|
||||
<Icon name="folder" size="sm" className={css.dashlistCardIcon} aria-hidden="true" />
|
||||
<Text color="secondary" variant="bodySmall" element="p">
|
||||
{locationInfo?.name}
|
||||
</Text>
|
||||
<Stack direction="column" justifyContent="space-between" height="100%">
|
||||
<Stack justifyContent="space-between" alignItems="start">
|
||||
<Link
|
||||
className={css.dashlistCardLink}
|
||||
href={url}
|
||||
aria-label={dashboard.name}
|
||||
title={dashboard.name}
|
||||
onClick={onCardLinkClick}
|
||||
>
|
||||
{shortTitle}
|
||||
</Link>
|
||||
<StarToolbarButton
|
||||
title={dashboard.name}
|
||||
group="dashboard.grafana.app"
|
||||
kind="Dashboard"
|
||||
id={dashboard.uid}
|
||||
onStarChange={onStarChange}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{showFolderNames && locationInfo && (
|
||||
<Stack alignItems="start" direction="row" gap={0.5} height="25%">
|
||||
<Icon name="folder" size="sm" className={css.dashlistCardIcon} aria-hidden="true" />
|
||||
<Text color="secondary" variant="bodySmall" element="p">
|
||||
{locationInfo?.name}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -32,6 +32,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
height: '100%',
|
||||
paddingTop: theme.spacing(1.5),
|
||||
|
||||
'&:hover': {
|
||||
backgroundImage: gradient,
|
||||
@@ -41,5 +42,8 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
dashlistCardIcon: css({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
dashlistCardLink: css({
|
||||
paddingTop: theme.spacing(0.5),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user