Merge remote-tracking branch 'origin/main' into resource-store

This commit is contained in:
Ryan McKinley 2024-06-20 18:07:48 +03:00
commit 83df3bdec8
51 changed files with 502 additions and 282 deletions

View File

@ -0,0 +1,79 @@
---
draft: true
aliases:
- ../administration/reports/
- ../enterprise/export-pdf/
- ../enterprise/reporting/
- ../panels/create-reports/
- reporting/
keywords:
- grafana
- announcement
labels:
products:
- cloud
- enterprise
menuTitle: Announcement banner
title: Create and configure announcement banner
description: Creat a banner to show important updates and information at the top of on every page
refs:
rbac:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
---
# Create and configure announcement banner
Announcement banner allows you to show important updates and information at the top of every page in Grafana. You can use the announcement banner to communicate important information to your users, such as maintenance windows, new features, or other important updates.
## Create or update an announcement banner
Only organization administrators can create announcement banner by default. You can customize who can create announcement banner with [Role-based access control](ref:rbac).
To create or update an announcement banner, follow these steps:
1. Click **Administration > General > Announcement banner** in the side navigation menu.
The Announcement banner page allows you to view, create and update the settings for a notification banner. Only one banner can be created at a time.
2. Toggle the **Enable** switch on to enable the announcement banner. It can be toggled off at any time to disable the banner.
3. Enter the **Message** for the announcement banner.
The message field supports Markdown. To add a header, use the following syntax:
```markdown
### Header
```
To add a link, use the following syntax:
```markdown
[link text](https://www.example.com)
```
The preview of the configured banner will appear on top of the form, under the **Preview** section.
4. Select the banner's start date and time in the **Starts** field.
By default, the banner starts being displayed immediately. You can set a future date and time for the banner to start displaying.
5. Select the banner's end date and time in the **Ends** field.
By default, the banner is displayed indefinitely. You can set a future date and time for the banner to stop displaying.
6. Select the banner's visibility.
**Everyone** - The banner is visible to all users, including on login page.
**Authenticated users** - The banner is visible to only authenticated users.
7. Select the type of banner in the **Variant** field.
This will determine the color of the banner's background.
8. Click **Save** to save the banner settings.
The banner will now be displayed at the top of every page in Grafana.

View File

@ -22,6 +22,11 @@ Use the Grafana Alerting - Telegram integration to send [Telegram](https://teleg
## Before you begin
### Telegram limitation
Telegram messages are limited to 4096 UTF-8 characters. If you use a `parse_mode` other than `None`, truncation may result in an invalid message, causing the notification to fail.
For longer messages, we recommend using an alternative contact method.
### Telegram bot API token and chat ID
To integrate Grafana with Telegram, you need to obtain a Telegram **bot API token** and a **chat ID** (i.e., the ID of the Telegram chat where you want to receive the alert notifications).

View File

@ -50,9 +50,11 @@ refs:
Usage insights enables you to have a better understanding of how your Grafana instance is used.
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
> Grafana Cloud insights logs include additional fields with their own dashboards.
> Read more in the [Grafana Cloud documentation](/docs/grafana-cloud/usage-insights/).
{{< admonition type="note" >}}
Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](https://grafana.com/docs/grafana-cloud/).
Grafana Cloud insights logs include additional fields with their own dashboards.
Read more in the [Grafana Cloud documentation](https://grafana.com/docs/grafana-cloud/account-management/usage-insights/).
{{< /admonition >}}
The usage insights feature collects a number of aggregated data and stores them in the database:
@ -77,7 +79,7 @@ For every dashboard and data source, you can access usage information.
To see dashboard usage information, click the dashboard insights icon in the header.
{{< figure src="/media/docs/grafana/dashboards/screenshot-dashboard-insights.png" max-width="400px" class="docs-image--no-shadow" alt="Dashboard insights icon" >}}
{{< figure src="/media/docs/grafana/dashboards/screenshot-dashboard-insights-11.2.png" max-width="400px" class="docs-image--no-shadow" alt="Dashboard insights icon" >}}
Dashboard insights show the following information:

View File

@ -19,22 +19,22 @@ refs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#special-data-sources
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#special-data-sources
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
visualization-specific-options:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/
configure-standard-options:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-standard-options/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-standard-options/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-standard-options/
configure-value-mappings:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-value-mappings/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-value-mappings/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-value-mappings/
generative-ai-features:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/manage-dashboards/#set-up-generative-ai-features-for-dashboards
@ -44,12 +44,12 @@ refs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-thresholds/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-thresholds/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-thresholds/
data-sources:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
add-a-data-source:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#add-a-data-source
@ -69,17 +69,12 @@ refs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-panel-options/#configure-repeating-panels
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-panel-options/#configure-repeating-panels
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options/#configure-repeating-panels
override-field-values:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
dashboard:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#special-data-sources
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
---
## Create a dashboard
@ -95,7 +90,7 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
**To create a dashboard**:
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Click **New** and select **New Dashboard**.
1. On the empty dashboard, click **+ Add visualization**.
@ -104,7 +99,7 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
1. In the dialog box that opens, do one of the following:
- Select one of your existing data sources.
- Select one of the Grafana's [built-in special data sources](ref:built-in-special-data-sources).
- Select one of the Grafana [built-in special data sources](ref:built-in-special-data-sources).
- Click **Configure a new data source** to set up a new one (Admins only).
{{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source modal" >}}
@ -115,14 +110,10 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
For more information about data sources, refer to [Data sources](ref:data-sources) for specific guidelines.
1. Write or construct a query in the query language of your data source.
1. Click the Refresh dashboard icon to query the data source.
![Refresh dashboard icon](/media/docs/grafana/dashboards/screenshot-refresh-dashboard-9.5.png)
1. Click **Refresh** to query the data source.
1. In the visualization list, select a visualization type.
![Visualization selector](/media/docs/grafana/dashboards/screenshot-select-visualization-9-5.png)
![Visualization selector](/media/docs/grafana/dashboards/screenshot-select-visualization-11-2.png)
Grafana displays a preview of your query results with the visualization applied.
@ -139,30 +130,35 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
- [Configure thresholds](ref:configure-thresholds)
- [Configure standard options](ref:configure-standard-options)
1. When you've finished editing your panel, click **Save** to save the dashboard.
1. When you've finished editing your panel, click **Save dashboard**.
Alternatively, click **Apply** if you want to see your changes applied to the dashboard first. Then click the save icon in the dashboard header.
Alternatively, click **Back to dashboard** if you want to see your changes applied to the dashboard first. Then click **Save dashboard** when you're ready.
1. Enter a title and description for your dashboard or have Grafana create them using [generative AI features](ref:generative-ai-features).
1. Select a folder, if applicable.
1. Click **Save**.
1. To add more panels to the dashboard, click **Add** in the dashboard header and select **Visualization** in the drop-down.
1. To add more panels to the dashboard, click **Back to dashboard**.
Then click **Add** in the dashboard header and select **Visualization** in the drop-down.
![Add drop-down](/media/docs/grafana/dashboards/screenshot-add-dropdown-10.0.png)
When you add additional panels to the dashboard, you're taken straight to the **Edit panel** view.
## Copy an existing dashboard
1. When you've saved all the changes you want to make to the dashboard, click **Exit edit**.
To copy an existing dashboard, follow these steps:
Now, when you want to make more changes to the saved dashboard, click **Edit** in the top-right corner.
1. Click **Dashboards** in the primary menu.
1. Open the dashboard to be copied.
1. Click **Settings** (gear icon) in the top right of the dashboard.
1. Click **Save as** in the top-right corner of the dashboard.
## Copy a dashboard
To copy a dashboard, follow these steps:
1. Click **Dashboards** in the main menu.
1. Open the dashboard you want to copy.
1. Click **Edit** in top-right corner.
1. Click the **Save dashboard** drop-down and select **Save as copy**.
1. (Optional) Specify the name, folder, description, and whether or not to copy the original dashboard tags for the copied dashboard.
By default, the copied dashboard has the same name as the original dashboard with the word "Copy" appended and is located in the same folder.
By default, the copied dashboard has the same name as the original dashboard with the word "Copy" appended and is in the same folder.
1. Click **Save**.
@ -178,7 +174,7 @@ To see an example of repeating rows, refer to [Dashboard with repeating rows](ht
**To configure repeating rows:**
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Navigate to the dashboard you want to work on.
1. At the top of the dashboard, click **Add** and select **Row** in the drop-down.
@ -192,7 +188,7 @@ To provide context to dashboard users, add the variable to the row title.
### Repeating rows and the Dashboard special data source
If a row includes panels using the special [Dashboard](ref:dashboard) data source&mdash;the data source that uses a result set from another panel in the same dashboard&mdash;then corresponding panels in repeated rows will reference the panel in the original row, not the ones in the repeated rows.
If a row includes panels using the special [Dashboard data source](ref:built-in-special-data-sources)&mdash;the data source that uses a result set from another panel in the same dashboard&mdash;then corresponding panels in repeated rows will reference the panel in the original row, not the ones in the repeated rows.
For example, in a dashboard:
@ -205,7 +201,7 @@ For example, in a dashboard:
You can place a panel on a dashboard in any location.
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Navigate to the dashboard you want to work on.
1. Click the panel title and drag the panel to the new location.
@ -213,6 +209,6 @@ You can place a panel on a dashboard in any location.
You can size a dashboard panel to suits your needs.
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Navigate to the dashboard you want to work on.
1. To adjust the size of the panel, click and drag the lower-right corner of the panel.

View File

@ -26,19 +26,19 @@ weight: 500
refs:
data-links:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#data-links
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#data-links
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-data-links/
data-link-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#data-link-variables
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#data-link-variables
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-data-links/#data-link-variables
dashboard-url-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/create-dashboard-url-variables/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/create-dashboard-url-variables/
destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/create-dashboard-url-variables/
---
# Manage dashboard links
@ -83,51 +83,64 @@ Once you've added a dashboard link, it appears in the upper right corner of your
Add links to other dashboards at the top of your current dashboard.
1. While viewing the dashboard you want to link, click the gear at the top of the screen to open **Dashboard settings**.
1. Click **Links** and then click **Add Dashboard Link** or **New**.
1. In **Type**, select **dashboards**.
1. Select link options:
- **With tags** Enter tags to limit the linked dashboards to only the ones with the tags you enter. Otherwise, Grafana includes links to all other dashboards.
- **As dropdown** If you are linking to lots of dashboards, then you probably want to select this option and add an optional title to the dropdown. Otherwise, Grafana displays the dashboard links side by side across the top of your dashboard.
- **Time range** Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now
- **Variable values** Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link. For more information, see [Dashboard URL variables](ref:dashboard-url-variables).
- **Open in new tab** Select this option if you want the dashboard link to open in a new tab or window.
1. Click **Add**.
1. In the dashboard you want to link, click **Edit**.
1. Click **Settings**.
1. Go to the **Links** tab and then click **Add dashboard link**.
The default link type is **Dashboards**.
1. In the **With tags** drop-down, enter tags to limit the linked dashboards to only the ones with the tags you enter.
If you don't add any tags, Grafana includes links to all other dashboards.
1. Set link options:
- **Show as dropdown** If you are linking to lots of dashboards, then you probably want to select this option and add an optional title to the dropdown. Otherwise, Grafana displays the dashboard links side by side across the top of your dashboard.
- **Include current time range** Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now
- **Include current template variable values** Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link. For more information, see [Dashboard URL variables](ref:dashboard-url-variables).
- **Open link in new tab** Select this option if you want the dashboard link to open in a new tab or window.
1. Click **Save dashboard** in the top-right corner.
1. Click **Back to dashboard** and then **Exit edit**.
### Add a URL link to a dashboard
Add a link to a URL at the top of your current dashboard. You can link to any available URL, including dashboards, panels, or external sites. You can even control the time range to ensure the user is zoomed in on the right data in Grafana.
1. While viewing the dashboard you want to link, click the gear at the top of the screen to open **Dashboard settings**.
1. Click **Links** and then click **Add Dashboard Link** or **New**.
1. In **Type**, select **link**.
1. Select link options:
- **Url** Enter the URL you want to link to. Depending on the target, you might want to include field values. **Example:** https://github.com/grafana/grafana/issues/new?title=Dashboard%3A%20HTTP%20Requests
- **Title** Enter the title you want the link to display.
- **Tooltip** Enter the tooltip you want the link to display when the user hovers their mouse over it.
- **Icon** Choose the icon you want displayed with the link.
- **Time range** Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now
- `from` - Defines the lower limit of the time range, specified in ms epoch.
- `to` - Defines the upper limit of the time range, specified in ms epoch.
- `time` and `time.window` - Define a time range from `time-time.window/2` to `time+time.window/2`. Both params should be specified in ms. For example `?time=1500000000000&time.window=10000` will result in 10s time range from 1499999995000 to 1500000005000.
- **Variable values** Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link. Here is the variable format: `https://${you-domain}/path/to/your/dashboard?var-${template-variable1}=value1&var-{template-variable2}=value2` **Example:** https://play.grafana.org/d/000000074/alerting?var-app=backend&var-server=backend_01&var-server=backend_03&var-interval=1h
- **Open in new tab** Select this option if you want the dashboard link to open in a new tab or window.
1. Click **Add**.
1. In the dashboard you want to link, click **Edit**.
1. Click **Settings**.
1. Go to the **Links** tab and then click **Add dashboard link**.
1. In the **Type** drop-down, select **Link**.
1. In the **URL** field, enter the URL to which you want to link.
Depending on the target, you might want to include field values. **Example:** https://github.com/grafana/grafana/issues/new?title=Dashboard%3A%20HTTP%20Requests
1. In the **Tooltip** field, enter the tooltip you want the link to display when the user hovers their mouse over it.
1. In the **Icon** drop-down, choose the icon you want displayed with the link.
1. Set link options; by default, these options are enabled for URL links:
- **Include current time range** Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now
- **Include current template variable values** Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link.
- **Open link in new tab** Select this option if you want the dashboard link to open in a new tab or window.
1. Click **Save dashboard** in the top-right corner.
1. Click **Back to dashboard** and then **Exit edit**.
### Update a dashboard link
To change or update an existing dashboard link, follow this procedure.
To change or update a dashboard link, follow this procedure.
1. In Dashboard Settings, on the Links tab, click the existing link that you want to edit.
1. Change the settings and then click **Update**.
1. In the dashboard settings, on the **Links** tab, click the link that you want to edit.
1. Change the settings and then click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
## Duplicate a dashboard link
To duplicate an existing dashboard link, click the duplicate icon next to the existing link that you want to duplicate.
To duplicate a dashboard link, click the copy link icon next to the link that you want to duplicate.
### Delete a dashboard link
To delete an existing dashboard link, click the trash icon next to the duplicate icon that you want to delete.
To delete a dashboard link, click the red **X** next to the link that you want to delete and then **Delete**.
## Panel links
@ -144,7 +157,7 @@ Click the icon next to the panel title to see available panel links.
To use a keyboard shortcut to open the panel, hover over the panel and press `e`.
1. Expand the **Panel options** section, scroll down to Panel links.
1. Expand the **Panel options** section, scroll down to **Panel links**.
1. Click **Add link**.
1. Enter a **Title**. **Title** is a human-readable label for the link that will be displayed in the UI.
1. Enter the **URL** you want to link to.
@ -152,9 +165,10 @@ Click the icon next to the panel title to see available panel links.
- `from` - Defines the lower limit of the time range, specified in ms epoch.
- `to` - Defines the upper limit of the time range, specified in ms epoch.
- `time` and `time.window` - Define a time range from `time-time.window/2` to `time+time.window/2`. Both params should be specified in ms. For example `?time=1500000000000&time.window=10000` will result in 10s time range from 1499999995000 to 1500000005000.
1. If you want the link to open in a new tab, then select **Open in a new tab**.
1. Click **Save** to save changes and close the window.
1. Click **Save** in the upper right to save your changes to the dashboard.
1. If you want the link to open in a new tab, then select **Open in new tab**.
1. Click **Save** to save changes and close the dialog box.
1. Click **Save dashboard** in the top-right corner.
1. Click **Back to dashboard** and then **Exit edit**.
### Update a panel link
@ -167,8 +181,9 @@ Click the icon next to the panel title to see available panel links.
1. Find the link that you want to make changes to.
1. Click the Edit (pencil) icon to open the Edit link window.
1. Make any necessary changes.
1. Click **Save** to save changes and close the window.
1. Click **Save** in the upper right to save your changes to the dashboard.
1. Click **Save** to save changes and close the dialog box.
1. Click **Save dashboard** in the top-right corner.
1. Click **Back to dashboard** and then **Exit edit**.
### Delete a panel link
@ -180,4 +195,5 @@ Click the icon next to the panel title to see available panel links.
1. Expand the **Panel options** section, scroll down to Panel links.
1. Find the link that you want to delete.
1. Click the **X** icon next to the link you want to delete.
1. Click **Save** in the upper right to save your changes to the dashboard.
1. Click **Save dashboard** in the top-right corner.
1. Click **Back to dashboard** and then **Exit edit**.

View File

@ -59,9 +59,9 @@ You must have an authorized viewer permission to see an image rendered by a dire
The same permission is also required to view embedded links unless you have anonymous access permission enabled for your Grafana instance.
{{% admonition type="note" %}}
{{< admonition type="note" >}}
As of Grafana 8.0, anonymous access permission is not available in Grafana Cloud.
{{% /admonition %}}
{{< /admonition >}}
When you share a panel or dashboard as a snapshot, a snapshot (which is a panel or dashboard at the moment you take the snapshot) is publicly available on the web. Anyone with a link to it can access it. Because snapshots do not require any authorization to view, Grafana removes information related to the account it came from, as well as any sensitive data from the snapshot.
@ -69,13 +69,13 @@ When you share a panel or dashboard as a snapshot, a snapshot (which is a panel
You can share a dashboard as a direct link or as a snapshot. You can also export a dashboard.
{{% admonition type="note" %}}
{{< admonition type="note" >}}
If you change a dashboard, ensure that you save the changes before sharing.
{{% /admonition %}}
{{< /admonition >}}
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Click the dashboard you want to share.
1. Click the **Share** button at the top right of the screen.
1. Click **Share** in the top-right corner.
The share dialog opens and shows the Link tab.
@ -108,7 +108,7 @@ If you created a snapshot by mistake, click **Delete snapshot** in the dialog bo
To delete existing snapshots, follow these steps:
1. In the primary menu, click **Dashboards**.
1. Click **Dashboards** in the main menu.
1. Click **Snapshots** to go to the snapshots management page.
1. Click the red **x** next to the snapshot URL that you want to delete.
@ -120,7 +120,7 @@ The dashboard export action creates a Grafana JSON file that contains everything
1. Click **Dashboards** in the main menu.
1. Open the dashboard you want to export.
1. Click the **Share** icon in the top navigation bar.
1. Click **Share** in the top-right corner.
1. Click **Export**.
If you're exporting the dashboard to use in another instance, with different data source UIDs, enable the **Export for sharing externally** switch.
@ -139,12 +139,14 @@ A template variable of the type `Constant` is automatically hidden in the dashbo
You can generate and save PDF files of any dashboard.
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
{{< admonition type="note" >}}
Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
{{< /admonition >}}
1. Click **Dashboards** in the left-side menu.
1. Click **Dashboards** in the main menu.
1. Click the dashboard you want to share.
1. Click the **Share** button at the top right of the screen.
1. On the PDF tab, select a layout option for the exported dashboard: **Portrait** or **Landscape**.
1. Click **Share** in the top-right corner.
1. On the **PDF** tab, select a layout option for the exported dashboard: **Portrait** or **Landscape**.
1. Click **Save as PDF** to render the dashboard as a PDF file.
Grafana opens the PDF file in a new window or browser tab.
@ -210,7 +212,7 @@ If you created a snapshot by mistake, click **Delete snapshot** in the dialog bo
To delete existing snapshots, follow these steps:
1. In the primary menu, click **Dashboards**.
1. Click **Dashboards** in the main menu.
1. Click **Snapshots** to go to the snapshots management page.
1. Click the red **x** next to the snapshot URL that you want to delete.
@ -220,7 +222,9 @@ The snapshot is immediately deleted. You may need to clear your browser cache or
You can embed a panel using an iframe on another web site. A viewer must be signed into Grafana to view the graph.
**> Note:** As of Grafana 8.0, anonymous access permission is no longer available for Grafana Cloud.
{{< admonition type="note" >}}
As of Grafana 8.0, anonymous access permission is no longer available for Grafana Cloud.
{{< /admonition >}}
Here is an example of the HTML code:
@ -243,4 +247,4 @@ To create a library panel from the **Share Panel** dialog:
1. In **Library panel name**, enter the name.
1. In **Save in folder**, select the folder in which to save the library panel. By default, the root level is selected.
1. Click **Create library panel** to save your changes.
1. Save the dashboard.
1. Click **Save dashboard**.

View File

@ -19,16 +19,18 @@ refs:
query-transform-data:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/
panel-inspector:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-inspector/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-inspector/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-inspector/
logs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/logs/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/logs/
destination: grafana-cloud/visualizations/panels-visualizations/visualizations/logs/
---
# InfluxDB query editor

View File

@ -14,7 +14,7 @@ weight: 200
# Build your first dashboard
This topic helps you get started with Grafana and build your first dashboard using the built-in `Grafana` data source. To learn more about Grafana, refer to [Introduction to Grafana]({{< relref "../introduction" >}}).
This topic helps you get started with Grafana and build your first dashboard using the built-in `Grafana` data source. To learn more about Grafana, refer to [Introduction to Grafana](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/introduction/).
{{% admonition type="note" %}}
Grafana also offers a [free account with Grafana Cloud](/signup/cloud/connect-account?pg=gsdocs) to help getting started even easier and faster. You can install Grafana to self-host or get a free Grafana Cloud account.
@ -22,7 +22,7 @@ Grafana also offers a [free account with Grafana Cloud](/signup/cloud/connect-ac
#### Install Grafana
Grafana can be installed on many different operating systems. For a list of the minimum hardware and software requirements, as well as instructions on installing Grafana, refer to [Install Grafana]({{< relref "../setup-grafana/installation" >}}).
Grafana can be installed on many different operating systems. For a list of the minimum hardware and software requirements, as well as instructions on installing Grafana, refer to [Install Grafana](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/installation/).
#### Sign in to Grafana
@ -39,18 +39,18 @@ To sign in to Grafana for the first time:
1. Click **OK** on the prompt and change your password.
{{% admonition type="note" %}}
{{< admonition type="note" >}}
We strongly recommend that you change the default administrator password.
{{% /admonition %}}
{{< /admonition >}}
#### Create a dashboard
If you've already set up a data source that you know how to query, refer to [Create a dashboard]({{< relref "../dashboards/build-dashboards/create-dashboard" >}}) instead.
If you've already set up a data source that you know how to query, refer to [Create a dashboard](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/create-dashboard/) instead.
To create your first dashboard using the built-in `-- Grafana --` data source:
1. Click **Dashboards** in the left-side menu.
1. On the Dashboards page, click **New** and select **New Dashboard** from the drop-down menu.
1. Click **Dashboards** in the main menu.
1. On the **Dashboards** page, click **New** and select **New Dashboard** from the drop-down menu.
1. On the dashboard, click **+ Add visualization**.
![Empty dashboard state](/media/docs/grafana/dashboards/empty-dashboard-10.2.png)
@ -59,35 +59,33 @@ To create your first dashboard using the built-in `-- Grafana --` data source:
{{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source dialog box" >}}
This configures your [query]({{< relref "../panels-visualizations/query-transform-data#add-a-query" >}}) and generates the Random Walk dashboard.
This configures your [query](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#add-a-query) and generates the Random Walk dashboard.
1. Click the Refresh dashboard icon to query the data source.
1. Click **Refresh** to query the data source.
1. When you've finished editing your panel, click **Save dashboard**.
![Refresh dashboard icon](/media/docs/grafana/dashboards/screenshot-refresh-dashboard-9.5.png)
1. When you've finished editing your panel, click **Save** to save the dashboard.
Alternatively, click **Apply** if you want to see your changes applied to the dashboard first. Then click the save icon in the dashboard header.
Alternatively, click **Back to dashboard** if you want to see your changes applied to the dashboard first. Then click **Save dashboard** when you're ready.
1. Add a descriptive title for the dashboard, or have Grafana create one using [generative AI features](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/manage-dashboards#set-up-generative-ai-features-for-dashboards), and then click **Save**.
1. Click **Back to dashboard** and then **Exit edit**.
Congratulations, you have created your first dashboard and it's displaying results.
#### Next steps
Continue to experiment with what you have built, try the [explore workflow]({{< relref "../explore" >}}) or another visualization feature. Refer to [Data sources]({{< relref "../datasources" >}}) for a list of supported data sources and instructions on how to [add a data source]({{< relref "../datasources#add-a-data-source" >}}). The following topics will be of interest to you:
Continue to experiment with what you have built, try the [explore workflow](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/explore/) or another visualization feature. Refer to [Data sources](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/datasources/) for a list of supported data sources and instructions on how to [add a data source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/datasources/#add-a-data-source). The following topics will be of interest to you:
- [Panels and visualizations]({{< relref "../panels-visualizations" >}})
- [Dashboards]({{< relref "../dashboards" >}})
- [Keyboard shortcuts]({{< relref "../dashboards/use-dashboards#keyboard-shortcuts" >}})
- [Panels and visualizations](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/)
- [Dashboards](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/)
- [Keyboard shortcuts](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/use-dashboards/#keyboard-shortcuts)
- [Plugins](/grafana/plugins?orderBy=weight&direction=asc)
##### Admins
The following topics are of interest to Grafana server admin users:
- [Grafana configuration]({{< relref "../setup-grafana/configure-grafana" >}})
- [Authentication]({{< relref "../setup-grafana/configure-security/configure-authentication" >}})
- [User permissions and roles]({{< relref "../administration/roles-and-permissions" >}})
- [Provisioning]({{< relref "../administration/provisioning" >}})
- [Grafana CLI]({{< relref "../cli" >}})
- [Grafana configuration](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/)
- [Authentication](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-authentication/)
- [User permissions and roles](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/)
- [Provisioning](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/provisioning/)
- [Grafana CLI](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/cli/)

View File

@ -250,5 +250,5 @@ If you want to add all of the current dashboard's variables to the URL, then use
1. If you want the link to open in a new tab, then toggle the **Open in a new tab** switch.
1. Click **Save** to save changes and close the dialog box.
1. Click **Apply** to see your changes in the dashboard.
1. Click the **Save dashboard** icon to save your changes to the dashboard.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.

View File

@ -244,7 +244,8 @@ To add a field override, follow these steps:
1. Select the field option that you want to apply.
1. Continue to add overrides to this field by clicking **Add override property**.
1. Add as many overrides as you need.
1. When you're finished, click **Save** to save all panel edits to the dashboard.
1. When you're finished, click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
## Edit a field override
@ -259,5 +260,7 @@ To edit a field override, follow these steps:
- Edit settings on existing overrides or field selection parameters.
- Delete existing override properties by clicking the **X** next to the property.
- Delete an override entirely by clicking the trash icon at the top-right corner.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
The changes you make take effect immediately.

View File

@ -95,7 +95,8 @@ To configure repeating panels, follow these steps:
- **Vertical** - Arrange panels in a column. The width of repeated panels is the same as the original, repeated panel.
1. If you selected **Horizontal** in the previous step, select a value in the **Max per row** drop-down list to control the maximum number of panels that can be in a row.
1. Click **Save**.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
1. To propagate changes to all panels, reload the dashboard.
You can stop a panel from repeating by selecting **Disable repeating** in the **Repeat by variable** drop-down list.

View File

@ -174,6 +174,8 @@ You can add as many thresholds to a visualization as you want. Grafana automatic
1. Click the colored circle to the left of the threshold value to open the color picker, where you can update the threshold color.
1. Under **Thresholds mode**, select either **Absolute** or **Percentage**.
1. Under **Show thresholds**, set how the threshold is displayed or turn it off.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
To delete a threshold, navigate to the panel that contains the threshold and click the trash icon next to the threshold you want to remove.

View File

@ -185,3 +185,6 @@ The following image shows a table visualization with value mappings. If you want
1. Click **Update** to save the value mapping.
After you've added a mapping, the **Edit value mappings** button replaces the **Add value mappings** button. Click the edit button to add or update mappings.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.

View File

@ -52,7 +52,7 @@ refs:
In the panel editor, you can update all the elements of a visualization including the data source, queries, time range, and visualization display options.
![Panel editor](/media/docs/grafana/panels-visualizations/screenshot-panel-editor-view.png)
![Panel editor](/media/docs/grafana/panels-visualizations/screenshot-grafana-11.2-panel-editor.png)
This following sections describe the areas of the Grafana panel editor.
@ -60,26 +60,25 @@ This following sections describe the areas of the Grafana panel editor.
The header section lists the dashboard in which the panel appears and the following controls:
- **Discard:** Discards changes you have made to the panel since you last saved the dashboard.
- **Save:** Saves changes you made to the panel.
- **Apply:** Applies changes you made and closes the panel editor, returning you to the dashboard. You'll have to save the dashboard to persist the applied changes.
- **Back to dashboard** - Return to the dashboard with changes applied, but not yet saved.
- **Discard panel changes** - Discard changes you have made to the panel since you last saved the dashboard.
- **Save dashboard** - Save your changes to the dashboard.
## Visualization preview
The visualization preview section contains the following options:
- **Table view:** Convert any visualization to a table so you can see the data. Table views are helpful for troubleshooting. This view only contains the raw data. It doesn't include transformations you might have applied to the data or the formatting options available in the [Table](ref:table) visualization.
- **Fill:** The visualization preview fills the available space. If you change the width of the side pane or height of the bottom pane the visualization changes to fill the available space.
- **Actual:** The visualization preview has the exact size as the size on the dashboard. If not enough space is available, the visualization scales down preserving the aspect ratio.
- **Time range controls:** **Default** is either the browser local timezone or the timezone selected at a higher level.
- **Table view** - Convert any visualization to a table so you can see the data. Table views are helpful for troubleshooting. This view only contains the raw data. It doesn't include transformations you might have applied to the data or the formatting options available in the [Table](ref:table) visualization.
- **Time range controls** - **Default** is either the browser local timezone or the timezone selected at a higher level.
- **Refresh** - Query the data source.
## Data section
The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable).
- **Query tab:** Select your data source and enter queries here. For more information, refer to [Add a query](ref:add-a-query). When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Query** tab.
- **Transform tab:** Apply data transformations. For more information, refer to [Transform data](ref:transform-data).
- **Alert tab:** Write alert rules. For more information, refer to [the overview of Grafana Alerting](ref:the-overview-of-grafana-alerting).
- **Queries** - Select your data source and enter queries here. For more information, refer to [Add a query](ref:add-a-query). When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Queries** tab.
- **Transformations** - Apply data transformations. For more information, refer to [Transform data](ref:transform-data).
- **Alert** - Write alert rules. For more information, refer to [the overview of Grafana Alerting](ref:the-overview-of-grafana-alerting).
## Panel display options

View File

@ -58,6 +58,10 @@ export interface TempoQuery extends common.DataQuery {
* Defines the maximum number of spans per spanset that are returned from Tempo
*/
spss?: number;
/**
* For metric queries, the step size to use
*/
step?: string;
/**
* The type of the table that is used to display the search results
*/

View File

@ -8,8 +8,8 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/metrics"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@ -28,13 +28,13 @@ func (hs *HTTPServer) getCreatedSnapshotHandler() web.Handler {
if hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesSnapshots) {
namespaceMapper := request.GetNamespaceMapper(hs.Cfg)
return func(w http.ResponseWriter, r *http.Request) {
user, err := appcontext.User(r.Context())
user, err := identity.GetRequester(r.Context())
if err != nil || user == nil {
errhttp.Write(r.Context(), fmt.Errorf("no user"), w)
return
}
r.URL.Path = "/apis/dashboardsnapshot.grafana.app/v0alpha1/namespaces/" +
namespaceMapper(user.OrgID) + "/dashboardsnapshots/create"
namespaceMapper(user.GetOrgID()) + "/dashboardsnapshots/create"
hs.clientConfigProvider.DirectlyServeHTTP(w, r)
}
}

View File

@ -11,7 +11,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
@ -43,12 +43,12 @@ func (hs *HTTPServer) getDSQueryEndpoint() web.Handler {
// rewrite requests from /ds/query to the new query service
namespaceMapper := request.GetNamespaceMapper(hs.Cfg)
return func(w http.ResponseWriter, r *http.Request) {
user, err := appcontext.User(r.Context())
user, err := identity.GetRequester(r.Context())
if err != nil || user == nil {
errhttp.Write(r.Context(), fmt.Errorf("no user"), w)
return
}
r.URL.Path = "/apis/query.grafana.app/v0alpha1/namespaces/" + namespaceMapper(user.OrgID) + "/query"
r.URL.Path = "/apis/query.grafana.app/v0alpha1/namespaces/" + namespaceMapper(user.GetOrgID()) + "/query"
hs.clientConfigProvider.DirectlyServeHTTP(w, r)
}
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/mod/semver"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -26,7 +27,6 @@ import (
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
"github.com/grafana/grafana/pkg/services/apiserver/options"
"github.com/prometheus/client_golang/prometheus"
)
// TODO: this is a temporary hack to make rest.Connecter work with resource level routes
@ -96,6 +96,7 @@ func SetupConfig(
// Needs to run last in request chain to function as expected, hence we register it first.
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
handler = filters.WithRequester(handler)
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
handler = filters.WithAcceptHeader(handler)
handler = filters.WithPathRewriters(handler, pathRewriters)

View File

@ -0,0 +1,67 @@
package filters
import (
"net/http"
"slices"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog"
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
// WithRequester makes sure there is an identity.Requester in context
func WithRequester(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
requester, err := identity.GetRequester(ctx)
if err != nil {
// Find the kubernetes user info
info, ok := request.UserFrom(ctx)
if ok {
if info.GetName() == user.Anonymous {
requester = &identity.StaticRequester{
Namespace: identity.NamespaceAnonymous,
Name: info.GetName(),
Login: info.GetName(),
Permissions: map[int64]map[string][]string{},
}
}
if info.GetName() == user.APIServerUser ||
slices.Contains(info.GetGroups(), user.SystemPrivilegedGroup) {
orgId := int64(1)
requester = &identity.StaticRequester{
UserID: 1,
OrgID: orgId,
Name: info.GetName(),
Login: info.GetName(),
OrgRole: identity.RoleAdmin,
IsGrafanaAdmin: true,
Permissions: map[int64]map[string][]string{
orgId: {
"*": {"*"}, // all resources, all scopes
// Dashboards do not support wildcard action
// dashboards.ActionDashboardsRead: {"*"},
// dashboards.ActionDashboardsCreate: {"*"},
// dashboards.ActionDashboardsWrite: {"*"},
// dashboards.ActionDashboardsDelete: {"*"},
// dashboards.ActionFoldersCreate: {"*"},
// dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}, // access to read all folders
},
},
}
}
if requester != nil {
req = req.WithContext(identity.WithRequester(ctx, requester))
} else {
klog.V(5).Info("unable to map the k8s user to grafana requester", "user", info)
}
}
}
handler.ServeHTTP(w, req)
})
}

View File

@ -4,13 +4,7 @@ import (
"context"
"fmt"
k8suser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
grpccontext "github.com/grafana/grafana/pkg/services/grpcserver/context"
"github.com/grafana/grafana/pkg/services/user"
)
@ -20,11 +14,16 @@ type ctxUserKey struct{}
// WithUser adds the supplied SignedInUser to the context.
func WithUser(ctx context.Context, usr *user.SignedInUser) context.Context {
ctx = context.WithValue(ctx, ctxUserKey{}, usr)
// make sure it is also in the simplified version
if usr == nil || usr.IsNil() {
return identity.WithRequester(ctx, nil)
}
return identity.WithRequester(ctx, usr)
}
// User extracts the SignedInUser from the supplied context.
// Supports context set by appcontext.WithUser, gRPC server context, and HTTP ReqContext.
// Deprecated: use identity.GetRequester(ctx) when possible
func User(ctx context.Context) (*user.SignedInUser, error) {
// Set by appcontext.WithUser
u, ok := ctx.Value(ctxUserKey{}).(*user.SignedInUser)
@ -38,44 +37,32 @@ func User(ctx context.Context) (*user.SignedInUser, error) {
return grpcCtx.SignedInUser, nil
}
// Set by incoming HTTP request
c, ok := ctxkey.Get(ctx).(*contextmodel.ReqContext)
if ok && c.SignedInUser != nil {
return c.SignedInUser, nil
}
// Find the kubernetes user info
k8sUserInfo, ok := request.UserFrom(ctx)
if ok {
for _, group := range k8sUserInfo.GetGroups() {
switch group {
case k8suser.APIServerUser:
fallthrough
case k8suser.SystemPrivilegedGroup:
orgId := int64(1)
return &user.SignedInUser{
UserID: 1,
OrgID: orgId,
Name: k8sUserInfo.GetName(),
Login: k8sUserInfo.GetName(),
OrgRole: identity.RoleAdmin,
IsGrafanaAdmin: true,
Permissions: map[int64]map[string][]string{
orgId: {
"*": {"*"}, // all resources, all scopes
// Dashboards do not support wildcard action
dashboards.ActionDashboardsRead: {"*"},
dashboards.ActionDashboardsCreate: {"*"},
dashboards.ActionDashboardsWrite: {"*"},
dashboards.ActionDashboardsDelete: {"*"},
dashboards.ActionFoldersCreate: {"*"},
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}, // access to read all folders
},
},
}, nil
}
}
// If the identity was set via requester, but not appcontext, we can map values
// NOTE: this path
requester, _ := identity.GetRequester(ctx)
if requester != nil {
id := requester.GetID()
userId, _ := id.UserID()
orgId := requester.GetOrgID()
return &user.SignedInUser{
NamespacedID: id,
UserID: userId,
UserUID: requester.GetUID().ID(),
OrgID: orgId,
OrgName: requester.GetOrgName(),
OrgRole: requester.GetOrgRole(),
Login: requester.GetLogin(),
Email: requester.GetEmail(),
IsGrafanaAdmin: requester.GetIsGrafanaAdmin(),
Teams: requester.GetTeams(),
AuthID: requester.GetAuthID(),
AuthenticatedBy: requester.GetAuthenticatedBy(),
IDToken: requester.GetIDToken(),
Permissions: map[int64]map[string][]string{
0: requester.GetGlobalPermissions(),
orgId: requester.GetPermissions(),
},
}, nil
}
return nil, fmt.Errorf("a SignedInUser was not found in the context")

View File

@ -11,8 +11,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
grpccontext "github.com/grafana/grafana/pkg/services/grpcserver/context"
"github.com/grafana/grafana/pkg/services/user"
)
@ -52,11 +50,9 @@ func TestUserFromContext(t *testing.T) {
require.Equal(t, expected.UserID, actual.UserID)
})
t.Run("should return user set by HTTP ReqContext", func(t *testing.T) {
t.Run("should return user set as a requester", func(t *testing.T) {
expected := testUser()
ctx := ctxkey.Set(context.Background(), &contextmodel.ReqContext{
SignedInUser: expected,
})
ctx := identity.WithRequester(context.Background(), expected)
actual, err := appcontext.User(ctx)
require.NoError(t, err)
require.Equal(t, expected.UserID, actual.UserID)

View File

@ -5,8 +5,8 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
@ -19,7 +19,7 @@ func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
return authorizer.DecisionNoOpinion, "", nil
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "", err
}
@ -27,7 +27,7 @@ func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
if attr.GetName() == "" {
// Discourage use of the "list" command for non super admin users
if attr.GetVerb() == "list" && attr.GetResource() == v0alpha1.DashboardResourceInfo.GroupResource().Resource {
if !user.IsGrafanaAdmin {
if !user.GetIsGrafanaAdmin() {
return authorizer.DecisionDeny, "list summary objects (or connect as GrafanaAdmin)", err
}
}

View File

@ -9,8 +9,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
)
@ -73,7 +73,7 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/datasources"
)
@ -17,7 +17,7 @@ func (b *DataSourceAPIBuilder) GetAuthorizer() authorizer.Authorizer {
if !attr.IsResourceRequest() {
return authorizer.DecisionNoOpinion, "", nil
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}

View File

@ -7,9 +7,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
@ -84,7 +84,7 @@ func (q *scopedDatasourceProvider) Get(ctx context.Context, uid string) (*v0alph
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -7,8 +7,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/datasources"
@ -108,7 +108,7 @@ func (q *DefaultQuerier) Datasource(ctx context.Context, name string) (*v0alpha1
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -20,12 +21,11 @@ import (
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/grafana/grafana/pkg/apimachinery/identity"
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
"github.com/grafana/grafana/pkg/apiserver/builder"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/prometheus/client_golang/prometheus"
)
var _ builder.APIGroupBuilder = (*TestingAPIBuilder)(nil)
@ -216,7 +216,7 @@ func (b *TestingAPIBuilder) GetAuthorizer() authorizer.Authorizer {
}
// require a user
_, err = appcontext.User(ctx)
_, err = identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}

View File

@ -8,8 +8,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
@ -38,14 +38,14 @@ func (r *dummySubresourceREST) Connect(ctx context.Context, name string, opts ru
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
// This response object format is negotiated by k8s
dummy := &example.DummySubresource{
Info: fmt.Sprintf("%s/%s", info.Value, user.Login),
Info: fmt.Sprintf("%s/%s", info.Value, user.GetLogin()),
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View File

@ -11,9 +11,9 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
"github.com/grafana/grafana/pkg/services/dashboards"
@ -83,7 +83,7 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
@ -116,7 +116,7 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
@ -146,7 +146,7 @@ func (s *legacyStorage) Create(ctx context.Context,
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
@ -195,7 +195,7 @@ func (s *legacyStorage) Update(ctx context.Context,
return nil, false, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, false, err
}
@ -267,7 +267,7 @@ func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidatio
if err != nil {
return v, false, err // includes the not-found error
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, false, err
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/prometheus/client_golang/prometheus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -15,11 +16,11 @@ import (
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/apiserver/builder"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
@ -27,7 +28,6 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)
var _ builder.APIGroupBuilder = (*FolderAPIBuilder)(nil)
@ -190,7 +190,7 @@ func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
}
// require a user
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}

View File

@ -7,8 +7,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
@ -49,7 +49,7 @@ func (r *subAccessREST) Connect(ctx context.Context, name string, opts runtime.O
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -7,8 +7,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/folder"
)
@ -46,7 +46,7 @@ func (r *subCountREST) NewConnectOptions() (runtime.Object, bool, string) {
}
func (r *subCountREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -8,38 +8,38 @@ import (
k8suser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
var _ authenticator.RequestFunc = signedInUserAuthenticator
func signedInUserAuthenticator(req *http.Request) (*authenticator.Response, bool, error) {
ctx := req.Context()
signedInUser, err := appcontext.User(ctx)
signedInUser, err := identity.GetRequester(ctx)
if err != nil {
klog.V(5).Info("failed to get signed in user", "err", err)
return nil, false, nil
}
userInfo := &k8suser.DefaultInfo{
Name: signedInUser.Login,
UID: signedInUser.UserUID,
Name: signedInUser.GetLogin(),
UID: signedInUser.GetUID().ID(),
Groups: []string{},
// In order to faithfully round-trip through an impersonation flow, Extra keys MUST be lowercase.
// see: https://pkg.go.dev/k8s.io/apiserver@v0.27.1/pkg/authentication/user#Info
Extra: map[string][]string{},
}
for _, v := range signedInUser.Teams {
for _, v := range signedInUser.GetTeams() {
userInfo.Groups = append(userInfo.Groups, strconv.FormatInt(v, 10))
}
//
if signedInUser.IDToken != "" {
userInfo.Extra["id-token"] = []string{signedInUser.IDToken}
if signedInUser.GetIDToken() != "" {
userInfo.Extra["id-token"] = []string{signedInUser.GetIDToken()}
}
if signedInUser.OrgRole.IsValid() {
userInfo.Extra["user-instance-role"] = []string{string(signedInUser.OrgRole)}
if signedInUser.GetOrgRole().IsValid() {
userInfo.Extra["user-instance-role"] = []string{string(signedInUser.GetOrgRole())}
}
return &authenticator.Response{

View File

@ -6,7 +6,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/org"
@ -27,7 +27,7 @@ func newOrgIDAuthorizer(orgService org.Service) *orgIDAuthorizer {
}
func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
signedInUser, err := appcontext.User(ctx)
signedInUser, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
}
@ -51,12 +51,16 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
}
// Quick check that the same org is used
if signedInUser.OrgID == info.OrgID {
if signedInUser.GetOrgID() == info.OrgID {
return authorizer.DecisionNoOpinion, "", nil
}
// Check if the user has access to the specified org
query := org.GetUserOrgListQuery{UserID: signedInUser.UserID}
userId, err := signedInUser.GetID().UserID()
if err != nil {
return authorizer.DecisionDeny, "unable to get userId", err
}
query := org.GetUserOrgListQuery{UserID: userId}
result, err := auth.org.GetUserOrgList(ctx, &query)
if err != nil {
return authorizer.DecisionDeny, "error getting user org list", err
@ -68,5 +72,5 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
}
}
return authorizer.DecisionDeny, fmt.Sprintf("user %d is not a member of org %d", signedInUser.UserID, info.OrgID), nil
return authorizer.DecisionDeny, fmt.Sprintf("user %d is not a member of org %d", userId, info.OrgID), nil
}

View File

@ -6,7 +6,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/org"
)
@ -22,12 +22,13 @@ func newOrgRoleAuthorizer(orgService org.Service) *orgRoleAuthorizer {
}
func (auth orgRoleAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
signedInUser, err := appcontext.User(ctx)
signedInUser, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
}
switch signedInUser.OrgRole {
orgRole := signedInUser.GetOrgRole()
switch orgRole {
case org.RoleAdmin:
return authorizer.DecisionAllow, "", nil
case org.RoleEditor:
@ -35,21 +36,21 @@ func (auth orgRoleAuthorizer) Authorize(ctx context.Context, a authorizer.Attrib
case "get", "list", "watch", "create", "update", "patch", "delete", "put", "post":
return authorizer.DecisionAllow, "", nil
default:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(orgRole, a), nil
}
case org.RoleViewer:
switch a.GetVerb() {
case "get", "list", "watch":
return authorizer.DecisionAllow, "", nil
default:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(orgRole, a), nil
}
case org.RoleNone:
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(string(signedInUser.OrgRole), a), nil
return authorizer.DecisionDeny, errorMessageForGrafanaOrgRole(orgRole, a), nil
}
return authorizer.DecisionDeny, "", nil
}
func errorMessageForGrafanaOrgRole(grafanaOrgRole string, a authorizer.Attributes) string {
return fmt.Sprintf("Grafana org role (%s) didn't allow %s access on requested resource=%s, path=%s", grafanaOrgRole, a.GetVerb(), a.GetResource(), a.GetPath())
func errorMessageForGrafanaOrgRole(orgRole identity.RoleType, a authorizer.Attributes) string {
return fmt.Sprintf("Grafana org role (%s) didn't allow %s access on requested resource=%s, path=%s", orgRole, a.GetVerb(), a.GetResource(), a.GetPath())
}

View File

@ -6,7 +6,7 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
@ -27,7 +27,7 @@ func newStackIDAuthorizer(cfg *setting.Cfg) *stackIDAuthorizer {
}
func (auth stackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
signedInUser, err := appcontext.User(ctx)
signedInUser, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
}
@ -48,7 +48,7 @@ func (auth stackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attrib
if info.OrgID != 1 {
return authorizer.DecisionDeny, "cloud instance requires org 1", nil
}
if signedInUser.OrgID != 1 {
if signedInUser.GetOrgID() != 1 {
return authorizer.DecisionDeny, "user must be in org 1", nil
}

View File

@ -8,7 +8,7 @@ import (
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/setting"
)
@ -82,9 +82,9 @@ func ParseNamespace(ns string) (NamespaceInfo, error) {
func OrgIDForList(ctx context.Context) (int64, error) {
ns := request.NamespaceValue(ctx)
if ns == "" {
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if user != nil {
return user.OrgID, err
return user.GetOrgID(), err
}
return -1, err
}

View File

@ -160,6 +160,11 @@ func ProvideService(
req.URL.Path = "/"
}
if c.SignedInUser != nil {
ctx := appcontext.WithUser(req.Context(), c.SignedInUser)
req = req.WithContext(ctx)
}
resp := responsewriter.WrapForHTTP1Or2(c.Resp)
s.handler.ServeHTTP(resp, req)
}

View File

@ -8,7 +8,7 @@ import (
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/datasources"
)
@ -56,11 +56,11 @@ func (s *cachingLegacyDataSourceLookup) GetDataSourceFromDeprecatedFields(ctx co
if id == 0 && name == "" {
return nil, fmt.Errorf("either name or ID must be set")
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
key := fmt.Sprintf("%d/%s/%d", user.OrgID, name, id)
key := fmt.Sprintf("%d/%s/%d", user.GetOrgID(), name, id)
s.cacheMu.Lock()
defer s.cacheMu.Unlock()
@ -70,13 +70,13 @@ func (s *cachingLegacyDataSourceLookup) GetDataSourceFromDeprecatedFields(ctx co
}
ds, err := s.retriever.GetDataSource(ctx, &datasources.GetDataSourceQuery{
OrgID: user.OrgID,
OrgID: user.GetOrgID(),
Name: name,
ID: id,
})
if errors.Is(err, datasources.ErrDataSourceNotFound) && name != "" {
ds, err = s.retriever.GetDataSource(ctx, &datasources.GetDataSourceQuery{
OrgID: user.OrgID,
OrgID: user.GetOrgID(),
UID: name, // Sometimes name is actually the UID :(
})
}

View File

@ -5,7 +5,7 @@ import (
"errors"
"strings"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
@ -47,7 +47,7 @@ func LibraryPanelUIDScopeResolver(l *LibraryElementService, folderSvc folder.Ser
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}

View File

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
@ -95,7 +94,7 @@ func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user
}
func (p *Provider) GetDataSourceInstanceSettings(ctx context.Context, uid string) (*backend.DataSourceInstanceSettings, error) {
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
@ -115,7 +114,7 @@ func (p *Provider) PluginContextForDataSource(ctx context.Context, datasourceSet
return backend.PluginContext{}, plugins.ErrPluginNotRegistered
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return backend.PluginContext{}, err
}

View File

@ -5,11 +5,13 @@ import (
"fmt"
"strconv"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
"github.com/grafana/grafana/pkg/services/user"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type Authenticator struct{}
@ -82,16 +84,17 @@ func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grp
var _ grpc.StreamClientInterceptor = StreamClientInterceptor
func WrapContext(ctx context.Context) (context.Context, error) {
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
return ctx, err
}
// set grpc metadata into the context to pass to the grpc server
return metadata.NewOutgoingContext(ctx, metadata.Pairs(
"grafana-idtoken", user.IDToken,
"grafana-userid", strconv.FormatInt(user.UserID, 10),
"grafana-orgid", strconv.FormatInt(user.OrgID, 10),
"grafana-login", user.Login,
"grafana-idtoken", user.GetIDToken(),
"grafana-userid", user.GetID().ID(),
"grafana-useruid", user.GetUID().ID(),
"grafana-orgid", strconv.FormatInt(user.GetOrgID(), 10),
"grafana-login", user.GetLogin(),
)), nil
}

View File

@ -17,7 +17,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -358,7 +358,7 @@ func (s *sqlEntityServer) History(ctx context.Context, r *entity.EntityHistoryRe
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
ctxLogger.Error("error getting user from ctx", "error", err)
return nil, err
@ -573,7 +573,7 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest)
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
ctxLogger.Error("error getting user from ctx", "error", err)
return nil, err
@ -803,7 +803,7 @@ func (s *sqlEntityServer) Watch(w entity.EntityStore_WatchServer) error {
return err
}
user, err := appcontext.User(w.Context())
user, err := identity.GetRequester(w.Context())
if err != nil {
ctxLogger.Error("error getting user from ctx", "error", err)
return err
@ -1289,7 +1289,7 @@ func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.Referenc
return nil, err
}
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil {
ctxLogger.Error("error getting user from ctx", "error", err)
return nil, err

View File

@ -8,7 +8,7 @@ import (
"fmt"
"text/template"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/store/entity/db"
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
)
@ -27,7 +27,7 @@ func createETag(body []byte, meta []byte, status []byte) string {
// getCurrentUser returns a string identifying the user making a request with
// the given context.
func getCurrentUser(ctx context.Context) (string, error) {
user, err := appcontext.User(ctx)
user, err := identity.GetRequester(ctx)
if err != nil || user == nil {
return "", fmt.Errorf("%w: %w", ErrUserNotFoundInContext, err)
}

View File

@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/tsdb/grafanads"
@ -122,12 +122,12 @@ func (c *dsCache) getDS(ctx context.Context, uid string) (*dsVal, error) {
}
}
usr, err := appcontext.User(ctx)
usr, err := identity.GetRequester(ctx)
if err != nil {
return nil, nil // no user
}
v, ok := c.cache[usr.OrgID]
v, ok := c.cache[usr.GetOrgID()]
if !ok {
return nil, nil // org not found
}

View File

@ -129,6 +129,9 @@ type TempoQuery struct {
// Defines the maximum number of spans per spanset that are returned from Tempo
Spss *int64 `json:"spss,omitempty"`
// For metric queries, the step size to use
Step *string `json:"step,omitempty"`
// The type of the table that is used to display the search results
TableType *SearchTableType `json:"tableType,omitempty"`
}

View File

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { sortBy } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useFormContext, FieldErrors, FieldValues, Controller } from 'react-hook-form';
import { Controller, FieldErrors, FieldValues, useFormContext } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Alert, Button, Field, Select, useStyles2 } from '@grafana/ui';
@ -53,6 +53,7 @@ export function ChannelSubForm<R extends ChannelValues>({
const { control, watch, register, trigger, formState, setValue } = useFormContext();
const selectedType = watch(fieldName('type')) ?? defaultValues.type; // nope, setting "default" does not work at all.
const parse_mode = watch(fieldName('settings.parse_mode'));
const { loading: testingReceiver } = useUnifiedAlertingSelector((state) => state.testReceivers);
// TODO I don't like integration specific code here but other ways require a bigger refactoring
@ -122,13 +123,14 @@ export function ChannelSubForm<R extends ChannelValues>({
};
const notifier = notifiers.find(({ dto: { type } }) => type === selectedType);
const isTelegram = selectedType === 'telegram';
const isParseModeNone = parse_mode === 'None';
// if there are mandatory options defined, optional options will be hidden by a collapse
// if there aren't mandatory options, all options will be shown without collapse
const mandatoryOptions = notifier?.dto.options.filter((o) => o.required);
const optionalOptions = notifier?.dto.options.filter((o) => !o.required);
const contactPointTypeInputId = `contact-point-type-${pathPrefix}`;
return (
<div className={styles.wrapper} data-testid="item-container">
<div className={styles.topRow}>
@ -188,6 +190,14 @@ export function ChannelSubForm<R extends ChannelValues>({
</div>
{notifier && (
<div className={styles.innerContent}>
{isTelegram && !isParseModeNone && (
<Alert
title="Telegram messages are limited to 4096 UTF-8 characters.
If you use a `parse_mode` other than 'None', truncation may result in an invalid message, causing the notification to fail.
For longer messages, we recommend using an alternative contact method."
severity="warning"
></Alert>
)}
<ChannelOptions<R>
defaultValues={defaultValues}
selectedChannelOptions={mandatoryOptions?.length ? mandatoryOptions! : optionalOptions!}

View File

@ -51,6 +51,8 @@ composableKinds: DataQuery: {
groupBy?: [...#TraceqlFilter]
// The type of the table that is used to display the search results
tableType?: #SearchTableType
// For metric queries, the step size to use
step?: string
} @cuetsy(kind="interface") @grafana(TSVeneer="type")
#TempoQueryType: "traceql" | "traceqlSearch" | "serviceMap" | "upload" | "nativeSearch" | "traceId" | "clear" @cuetsy(kind="type")

View File

@ -56,6 +56,10 @@ export interface TempoQuery extends common.DataQuery {
* Defines the maximum number of spans per spanset that are returned from Tempo
*/
spss?: number;
/**
* For metric queries, the step size to use
*/
step?: string;
/**
* The type of the table that is used to display the search results
*/

View File

@ -438,7 +438,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
isTraceQlMetricsQuery(query: string): boolean {
// Check whether this is a metrics query by checking if it contains a metrics function
const metricsFnRegex =
/\|\s*(rate|count_over_time|avg_over_time|max_over_time|min_over_time|quantile_over_time|histogram_over_time)\s*\(/;
/\|\s*(rate|count_over_time|avg_over_time|max_over_time|min_over_time|quantile_over_time|histogram_over_time|compare)\s*\(/;
return !!query.trim().match(metricsFnRegex);
}
@ -643,11 +643,18 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
options: DataQueryRequest<TempoQuery>,
queryValue: string
): Observable<DataQueryResponse> => {
return this._request('/api/metrics/query_range', {
const requestData = {
query: queryValue,
start: options.range.from.unix(),
end: options.range.to.unix(),
}).pipe(
step: options.targets[0].step,
};
if (!requestData.step) {
delete requestData.step;
}
return this._request('/api/metrics/query_range', requestData).pipe(
map((response) => {
return {
data: formatTraceQLMetrics(queryValue, response.data),

View File

@ -44,11 +44,15 @@ export const TempoQueryBuilderOptions = React.memo<Props>(({ onChange, query })
const onTableTypeChange = (val: SearchTableType) => {
onChange({ ...query, tableType: val });
};
const onStepChange = (e: React.FormEvent<HTMLInputElement>) => {
onChange({ ...query, step: e.currentTarget.value });
};
const collapsedInfoList = [
`Limit: ${query.limit || DEFAULT_LIMIT}`,
`Spans Limit: ${query.spss || DEFAULT_SPSS}`,
`Table Format: ${query.tableType === SearchTableType.Traces ? 'Traces' : 'Spans'}`,
`Step: ${query.step || 'auto'}`,
];
return (
@ -87,6 +91,19 @@ export const TempoQueryBuilderOptions = React.memo<Props>(({ onChange, query })
onChange={onTableTypeChange}
/>
</EditorField>
<EditorField
label="Step"
tooltip="Defines the step for metric queries. Use duration notation, for example 30s or 1m"
>
<AutoSizeInput
className="width-4"
placeholder="auto"
type="string"
defaultValue={query.step}
onCommitChange={onStepChange}
value={query.step}
/>
</EditorField>
</QueryOptionGroup>
</EditorRow>
</>