mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'origin/main' into resource-store
This commit is contained in:
commit
83df3bdec8
79
docs/sources/administration/announcement-banner/_index.md
Normal file
79
docs/sources/administration/announcement-banner/_index.md
Normal 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.
|
@ -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).
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||

|
||||
|
||||
1. Click **Refresh** to query the data source.
|
||||
1. In the visualization list, select a visualization type.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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—the data source that uses a result set from another panel in the same dashboard—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)—the data source that uses a result set from another panel in the same dashboard—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.
|
||||
|
@ -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**.
|
||||
|
@ -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**.
|
||||
|
@ -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
|
||||
|
@ -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**.
|
||||
|
||||

|
||||
@ -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**.
|
||||
|
||||

|
||||
|
||||
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/)
|
||||
|
@ -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**.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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**.
|
||||
|
@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
67
pkg/apiserver/endpoints/filters/requester.go
Normal file
67
pkg/apiserver/endpoints/filters/requester.go
Normal 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)
|
||||
})
|
||||
}
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 :(
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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!}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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),
|
||||
|
@ -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>
|
||||
</>
|
||||
|
Loading…
Reference in New Issue
Block a user