Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync
This commit is contained in:
@@ -71,7 +71,14 @@ refs:
|
||||
|
||||
# Tempo data source
|
||||
|
||||
Grafana ships with built-in support for [Tempo](https://grafana.com/docs/tempo/latest/), a high-volume, minimal-dependency trace storage, open source tracing solution from Grafana Labs. This topic explains configuration and queries specific to the Tempo data source.
|
||||
Grafana ships with built-in support for [Tempo](https://grafana.com/docs/tempo/<TEMPO_VERSION>/), a high-volume, minimal-dependency trace storage, open source tracing solution from Grafana Labs.
|
||||
|
||||
To learn more about traces, refer to [Introduction to tracing](https://grafana.com/docs/tempo/<TEMPO_VERSION>/introduction/).
|
||||
|
||||
To use traces, you need you have an application or service that is instrumented to emit traces.
|
||||
Refer to the [Instrument for tracing](https://grafana.com/docs/tempo/<TEMPO_VERSION>/getting-started/instrumentation/) for more information.
|
||||
|
||||
## Add a data source
|
||||
|
||||
For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:data-source-management).
|
||||
Only users with the organization administrator role can add data sources.
|
||||
@@ -81,6 +88,8 @@ This video explains how to add data sources, including Loki, Tempo, and Mimir, t
|
||||
|
||||
{{< youtube id="cqHO0oYW6Ic" start="298" >}}
|
||||
|
||||
Once you've added the data source, you can [configure it]({{< relref "./configure-tempo-data-source/" >}}) so that your Grafana instance's users can create queries in its [query editor]({{< relref "./query-editor/" >}}) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore).
|
||||
## Learn more
|
||||
|
||||
After you've added the data source, you can [configure it](./configure-tempo-data-source/) so that your Grafana instance's users can create queries in its [query editor](./query-editor/) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore).
|
||||
|
||||
{{< section withDescriptions="true">}}
|
||||
|
||||
@@ -131,7 +131,7 @@ To use streaming, you need to:
|
||||
- Run Tempo version 2.2 or later, or Grafana Enterprise Traces (GET) version 2.2 or later, or use Grafana Cloud Traces.
|
||||
- Tempo must have `stream_over_http_enabled: true` for streaming to work.
|
||||
|
||||
For more information, refer to [Tempo GRPC API](https://grafana.com/docs/tempo/<TEMPO_VERSION>/api_docs/#tempo-grpc-api).
|
||||
For more information, refer to [Tempo gRPC API](https://grafana.com/docs/tempo/<TEMPO_VERSION>/api_docs/#tempo-grpc-api).
|
||||
|
||||
- For self-managed Tempo or GET instances: If your Tempo or GET instance is behind a load balancer or proxy that doesn't supporting gRPC or HTTP2, streaming may not work and should be deactivated.
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ Refer to the [Search using the TraceQL query builder documentation]({{< relref "
|
||||
|
||||
The **TraceQL** query editor lets you search by trace ID and write TraceQL queries using autocomplete.
|
||||
|
||||
Refer to the [TraceQL query editor documentation]({{< relref "./traceql-editor" >}}) to learn more about constructing queries using a code-editor-like experience.
|
||||
Refer to the [TraceQL query editor documentation](./traceql-editor) to learn more about constructing queries using a code-editor-like experience.
|
||||
|
||||

|
||||
|
||||
@@ -113,8 +113,8 @@ You can also search for a trace ID by entering it into the query field.
|
||||
|
||||
### Service graph view
|
||||
|
||||
Grafana’s **Service Graph** view uses metrics to display span request rates, error rates, and durations, as well as service graphs.
|
||||
Once the requirements are set up, this preconfigured view is immediately available.
|
||||
The **Service Graph** view uses metrics to display span request rates, error rates, and durations, as well as service graphs.
|
||||
After the requirements are set up, this preconfigured view is immediately available.
|
||||
|
||||
Using the service graph view, you can:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ labels:
|
||||
- oss
|
||||
menuTitle: Write TraceQL queries
|
||||
title: Write TraceQL queries with the editor
|
||||
weight: 300
|
||||
weight: 400
|
||||
refs:
|
||||
explore:
|
||||
- pattern: /docs/grafana/
|
||||
@@ -41,4 +41,4 @@ refs:
|
||||
[//]: # 'Shared content for the TraceQL query editor'
|
||||
[//]: # 'This content is located in /docs/sources/shared/datasources/tempo-editor-traceql.md'
|
||||
|
||||
{{< docs/shared source="grafana" lookup="datasources/tempo-editor-traceql.md" version="<GRAFANA VERSION>" >}}
|
||||
{{< docs/shared source="grafana" lookup="datasources/tempo-editor-traceql.md" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
@@ -56,4 +56,4 @@ To enable the TraceQL query builder in self-hosted Grafana through version 10.1,
|
||||
|
||||
[//]: # 'Shared content for the Search - TraceQL query builder'
|
||||
|
||||
{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" version="<GRAFANA VERSION>" >}}
|
||||
{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
description: Learn how to create TraceQL queries are structured.
|
||||
keywords:
|
||||
- queries
|
||||
- TraceQL
|
||||
labels:
|
||||
products:
|
||||
- cloud
|
||||
- enterprise
|
||||
- oss
|
||||
menuTitle: TraceQL query structure
|
||||
title: TraceQL query structure
|
||||
weight: 200
|
||||
---
|
||||
|
||||
# TraceQL query structure
|
||||
|
||||
[//]: # 'Shared content for best practices for traces'
|
||||
[//]: # 'This content is located in /tempo/docs/sources/shared/trace-structure.md'
|
||||
|
||||
{{< docs/shared source="tempo" lookup="traceql-query-structure.md" version="<TEMPO_VERSION>" >}}
|
||||
@@ -72,8 +72,8 @@ You use the Service Graph to detect performance issues; track increases in error
|
||||
|
||||
## Display the Service Graph
|
||||
|
||||
1. [Configure Grafana Alloy](https://grafana.com/docs/tempo/latest/configuration/grafana-alloy/) or [Tempo or GET](https://grafana.com/docs/tempo/latest/metrics-generator/service_graphs/#tempo) to generate Service Graph data.
|
||||
1. Link a Prometheus data source in the Tempo data source's [Service Graph]({{< relref "./configure-tempo-data-source#configure-service-graph" >}}) settings.
|
||||
1. [Configure Grafana Alloy](https://grafana.com/docs/tempo/<TEMPO_VERSION>/configuration/grafana-alloy/) or [Tempo or GET](https://grafana.com/docs/tempo/<TEMPO_VERSION>/metrics-generator/service_graphs/#tempo) to generate Service Graph data.
|
||||
1. Link a Prometheus data source in the Tempo data source's [Service Graph](./configure-tempo-data-source#configure-service-graph) settings.
|
||||
1. Navigate to [Explore](ref:explore).
|
||||
1. Select the Tempo data source.
|
||||
1. Select the **Service Graph** query type.
|
||||
@@ -113,10 +113,10 @@ To open the Service Graph view:
|
||||
1. Run the query.
|
||||
1. _(Optional)_ Filter your results.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
{{< admonition type="note" >}}
|
||||
Grafana uses the `traces_spanmetrics_calls_total` metric to display the name, rate, and error rate columns, and `traces_spanmetrics_latency_bucket` to display the duration column.
|
||||
These metrics must exist in your Prometheus data source.
|
||||
{{% /admonition %}}
|
||||
{{< /admonition >}}
|
||||
|
||||
To open a query in Prometheus with the span name of that row automatically set in the query, click a row in the **rate**, **error rate**, or **duration** columns.
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
description: Learn about how you can use tracing data in Grafana Cloud to query data,
|
||||
generate metrics, and link your tracing data with logs, metrics, and profiles.
|
||||
keywords:
|
||||
- Grafana
|
||||
- traces
|
||||
- tracing
|
||||
title: Use traces in Grafana
|
||||
weight: 275
|
||||
---
|
||||
|
||||
# Use traces in Grafana
|
||||
|
||||
Using traces, you can search for traces, generate metrics from spans, and link your tracing data with logs, metrics, and profiles.
|
||||
|
||||
This page provides a summary of how you can use tracing data in Grafana.
|
||||
|
||||
For general documentation on querying data sources in Grafana, refer to [Query and transform data](https://grafana.com/docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/).
|
||||
|
||||
[//]: # 'Shared content for best practices for traces'
|
||||
[//]: # 'This content is located in /tempo/docs/sources/shared/tempo-in-grafana.md'
|
||||
|
||||
{{< docs/shared source="tempo" lookup="tempo-in-grafana.md" version="<TEMPO_VERSION>" >}}
|
||||
+12
-2
@@ -12,7 +12,9 @@ labels:
|
||||
- oss
|
||||
menuTitle: Upload JSON trace file
|
||||
title: Upload a JSON trace file
|
||||
weight: 400
|
||||
weight: 900
|
||||
aliases:
|
||||
- ../json-trace-file/ # /docs/grafana/latest/datasources/tempo/json-trace-file
|
||||
---
|
||||
|
||||
# Upload a JSON trace file
|
||||
@@ -20,7 +22,15 @@ weight: 400
|
||||
You can upload a JSON file that contains a single trace and visualize it.
|
||||
If the file has multiple traces, Grafana visualizes the first trace.
|
||||
|
||||
**To download a trace or Service Graph through the inspector:**
|
||||
To upload a trace file:
|
||||
|
||||
1. Select **Explore** in Grafana.
|
||||
1. Select **Import trace** in the right corner.
|
||||
1. Upload your JSON trace file.
|
||||
|
||||
## Download a trace or service graph
|
||||
|
||||
To download a trace or Service Graph through the [Inspector panel](https://grafana.com/docs/grafana/<TEMPO_VERSION>/explore/explore-inspector/):
|
||||
|
||||
1. Open the inspector.
|
||||
1. Navigate to the **Data** tab.
|
||||
+6
-3
@@ -12,7 +12,10 @@ labels:
|
||||
- oss
|
||||
menuTitle: Link to a trace ID
|
||||
title: Link to a trace ID
|
||||
weight: 700
|
||||
weight: 800
|
||||
aliases:
|
||||
- ../link-trace-id/ # /docs/grafana/latest/datasources/tempo/link-trace-id/
|
||||
|
||||
refs:
|
||||
configure-grafana-feature-toggles:
|
||||
- pattern: /docs/grafana/
|
||||
@@ -69,10 +72,10 @@ You can link to Tempo traces from logs or metrics.
|
||||
|
||||
You can link to Tempo traces from logs in Loki, Elasticsearch, Splunk, and other logs data sources by configuring an internal link.
|
||||
|
||||
To configure this feature, see the [Derived fields]({{< relref "../loki#configure-derived-fields" >}}) section of the Loki data source docs or the [Data links]({{< relref "../elasticsearch#data-links" >}}) section of the Elasticsearch or Splunk data source docs.
|
||||
To configure this feature, refer to the [Derived fields](../../loki#configure-derived-fields) section of the Loki data source docs or the [Data links](../../elasticsearch#data-links) section of the Elasticsearch or Splunk data source docs.
|
||||
|
||||
## Link to a trace ID from metrics
|
||||
|
||||
You can link to Tempo traces from metrics in Prometheus data sources by configuring an exemplar.
|
||||
|
||||
To configure this feature, see the [introduction to exemplars](ref:exemplars) documentation.
|
||||
To configure this feature, refer to the [Exemplars](ref:exemplars) documentation.
|
||||
@@ -157,7 +157,8 @@ The node graph requires data to be returned from the data source in a specific f
|
||||
|
||||
## Service graph
|
||||
|
||||
A service graph visualizes span metrics, including rates, error rates, and durations (RED), along with service relationships. Once the requirements are configured, this pre-configured view is immediately available.
|
||||
A service graph visualizes rates, error rates, and durations (RED), along with service relationships.
|
||||
After the requirements are configured, this pre-configured view is immediately available.
|
||||
|
||||
For additional information refer to the following documentation:
|
||||
|
||||
|
||||
+20
-2
@@ -113,8 +113,26 @@ Sign in to Grafana and navigate to **Administration > Authentication > Configure
|
||||
|
||||
1. If you wish to [map user information from SAML assertions]({{< relref "../saml#assertion-mapping" >}}), complete the **Assertion attributes mappings** section.
|
||||
|
||||
You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership.
|
||||
To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-group-attribute-sync) documentation.
|
||||
If Azure is the Identity Provider over SAML there are caveats for the assertion attribute mappings. Due to how Azure interprets these attributes the full URL will need to be entered in the corresponding fields within the UI, which should match the URLs from the metadata XML. There are differences depending on whether it's a Role or Group claim vs other assertions which Microsoft has [documented](https://learn.microsoft.com/en-us/entra/identity-platform/reference-claims-customization#table-2-saml-restricted-claim-set).
|
||||
|
||||
Group and Role:
|
||||
|
||||
```
|
||||
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
|
||||
http://schemas.microsoft.com/ws/2008/06/identity/claims/groups
|
||||
http://schemas.microsoft.com/identity/claims/displayname
|
||||
```
|
||||
|
||||
Other Assertions:
|
||||
|
||||
```
|
||||
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
|
||||
```
|
||||
|
||||

|
||||
|
||||
You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership.
|
||||
To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-group-attribute-sync) documentation.
|
||||
|
||||
1. If you want to automatically assign users' roles based on their SAML roles, complete the **Role mapping** section.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('Solo Route', () => {
|
||||
cy.contains('uplot-main-div').should('not.exist');
|
||||
});
|
||||
|
||||
it('Can view solo repeated panel in scenes', () => {
|
||||
/*it('Can view solo repeated panel in scenes', () => {
|
||||
// open Panel Tests - Graph NG
|
||||
e2e.pages.SoloPanel.visit(
|
||||
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true'
|
||||
@@ -30,7 +30,7 @@ describe('Solo Route', () => {
|
||||
|
||||
e2e.components.Panels.Panel.title('server=A').should('exist');
|
||||
cy.contains('uplot-main-div').should('not.exist');
|
||||
});
|
||||
});*/
|
||||
|
||||
it('Can view solo in repeated row and panel in scenes', () => {
|
||||
// open Panel Tests - Graph NG
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
var PluginIDRepoName = "plugin"
|
||||
var fileProvisionedRepoPrefix = "file:"
|
||||
|
||||
// ProvisionedFileNameWithPrefix adds the `file:` prefix to the
|
||||
// provisioner name, to be used as the annotation for dashboards
|
||||
// provisioned from files
|
||||
func ProvisionedFileNameWithPrefix(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fileProvisionedRepoPrefix + name
|
||||
}
|
||||
|
||||
// GetProvisionedFileNameFromMeta returns the provisioner name
|
||||
// from a given annotation string, which is in the form file:<name>
|
||||
func GetProvisionedFileNameFromMeta(annotation string) (string, bool) {
|
||||
return strings.CutPrefix(annotation, fileProvisionedRepoPrefix)
|
||||
}
|
||||
|
||||
// SetPluginIDMeta sets the repo name to "plugin" and the path to the plugin ID
|
||||
func SetPluginIDMeta(obj unstructured.Unstructured, pluginID string) {
|
||||
if pluginID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[utils.AnnoKeyRepoName] = PluginIDRepoName
|
||||
annotations[utils.AnnoKeyRepoPath] = pluginID
|
||||
obj.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
// GetPluginIDFromMeta returns the plugin ID from the meta if the repo name is "plugin"
|
||||
func GetPluginIDFromMeta(obj utils.GrafanaMetaAccessor) string {
|
||||
if obj.GetRepositoryName() == PluginIDRepoName {
|
||||
return obj.GetRepositoryPath()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
@@ -312,7 +311,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
ts := time.Unix(origin_ts.Int64, 0)
|
||||
|
||||
repo := &utils.ResourceRepositoryInfo{
|
||||
Name: origin_name.String,
|
||||
Name: dashboard.ProvisionedFileNameWithPrefix(origin_name.String),
|
||||
Hash: origin_hash.String,
|
||||
Timestamp: &ts,
|
||||
}
|
||||
@@ -331,7 +330,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
meta.SetRepositoryInfo(repo)
|
||||
} else if plugin_id.String != "" {
|
||||
meta.SetRepositoryInfo(&utils.ResourceRepositoryInfo{
|
||||
Name: "plugin",
|
||||
Name: dashboard.PluginIDRepoName,
|
||||
Path: plugin_id.String,
|
||||
})
|
||||
}
|
||||
@@ -427,7 +426,7 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das
|
||||
out, err := a.dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{
|
||||
OrgID: orgId,
|
||||
Message: meta.GetMessage(),
|
||||
PluginID: service.GetPluginIDFromMeta(meta),
|
||||
PluginID: dashboard.GetPluginIDFromMeta(meta),
|
||||
Dashboard: simplejson.NewFromAny(dash.Spec.UnstructuredContent()),
|
||||
FolderUID: meta.GetFolder(),
|
||||
Overwrite: true, // already passed the revisionVersion checks!
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestScanRow(t *testing.T) {
|
||||
mockDB, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer mockDB.Close() // nolint:errcheck
|
||||
|
||||
pathToFile := "path/to/file"
|
||||
provisioner := provisioning.NewProvisioningServiceMock(context.Background())
|
||||
provisioner.GetDashboardProvisionerResolvedPathFunc = func(name string) string { return "provisioner" }
|
||||
store := &dashboardSqlAccess{
|
||||
namespacer: func(_ int64) string { return "default" },
|
||||
provisioning: provisioner,
|
||||
}
|
||||
|
||||
columns := []string{"orgId", "dashboard_id", "name", "folder_uid", "deleted", "plugin_id", "origin_name", "origin_path", "origin_hash", "origin_ts", "created", "createdBy", "createdByID", "updated", "updatedBy", "updatedByID", "version", "message", "data"}
|
||||
id := int64(100)
|
||||
title := "Test Dashboard"
|
||||
folderUID := "folder123"
|
||||
timestamp := time.Now()
|
||||
k8sTimestamp := v1.Time{Time: timestamp}
|
||||
version := int64(2)
|
||||
message := "updated message"
|
||||
createdUser := "creator"
|
||||
updatedUser := "updator"
|
||||
|
||||
t.Run("Should scan a valid row correctly", func(t *testing.T) {
|
||||
rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`))
|
||||
mock.ExpectQuery("SELECT *").WillReturnRows(rows)
|
||||
resultRows, err := mockDB.Query("SELECT *")
|
||||
require.NoError(t, err)
|
||||
defer resultRows.Close() // nolint:errcheck
|
||||
resultRows.Next()
|
||||
|
||||
row, err := store.scanRow(resultRows, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, row)
|
||||
require.Equal(t, "Test Dashboard", row.Dash.Name)
|
||||
require.Equal(t, version, row.RV) // rv should be the dashboard version
|
||||
require.Equal(t, v0alpha1.Unstructured{
|
||||
Object: map[string]interface{}{"key": "value"},
|
||||
}, row.Dash.Spec)
|
||||
require.Equal(t, "default", row.Dash.Namespace)
|
||||
require.Equal(t, &continueToken{orgId: int64(1), id: id}, row.token)
|
||||
|
||||
meta, err := utils.MetaAccessor(row.Dash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, id, meta.GetDeprecatedInternalID()) // nolint:staticcheck
|
||||
require.Equal(t, version, meta.GetGeneration()) // generation should be dash version
|
||||
require.Equal(t, k8sTimestamp, meta.GetCreationTimestamp())
|
||||
require.Equal(t, "user:"+createdUser, meta.GetCreatedBy()) // should be prefixed by user:
|
||||
require.Equal(t, "user:"+updatedUser, meta.GetUpdatedBy()) // should be prefixed by user:
|
||||
require.Equal(t, message, meta.GetMessage())
|
||||
require.Equal(t, folderUID, meta.GetFolder())
|
||||
})
|
||||
|
||||
t.Run("File provisioned dashboard should have annotations", func(t *testing.T) {
|
||||
rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "", "provisioner", pathToFile, "hashing", 100000, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`))
|
||||
mock.ExpectQuery("SELECT *").WillReturnRows(rows)
|
||||
resultRows, err := mockDB.Query("SELECT *")
|
||||
require.NoError(t, err)
|
||||
defer resultRows.Close() // nolint:errcheck
|
||||
resultRows.Next()
|
||||
|
||||
row, err := store.scanRow(resultRows, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, row)
|
||||
|
||||
meta, err := utils.MetaAccessor(row.Dash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "file:provisioner", meta.GetRepositoryName()) // should be prefixed by file:
|
||||
require.Equal(t, "../"+pathToFile, meta.GetRepositoryPath()) // relative to provisioner
|
||||
require.Equal(t, "hashing", meta.GetRepositoryHash())
|
||||
ts, err := meta.GetRepositoryTimestamp()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(100000), ts.Unix())
|
||||
})
|
||||
|
||||
t.Run("Plugin provisioned dashboard should have annotations", func(t *testing.T) {
|
||||
rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "slo", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`))
|
||||
mock.ExpectQuery("SELECT *").WillReturnRows(rows)
|
||||
resultRows, err := mockDB.Query("SELECT *")
|
||||
require.NoError(t, err)
|
||||
defer resultRows.Close() // nolint:errcheck
|
||||
resultRows.Next()
|
||||
|
||||
row, err := store.scanRow(resultRows, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, row)
|
||||
|
||||
meta, err := utils.MetaAccessor(row.Dash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "plugin", meta.GetRepositoryName())
|
||||
require.Equal(t, "slo", meta.GetRepositoryPath()) // the ID of the plugin
|
||||
require.Equal(t, "", meta.GetRepositoryHash()) // hash is not used on plugins
|
||||
})
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"google.golang.org/grpc"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
)
|
||||
|
||||
type DashboardSearchClient struct {
|
||||
@@ -29,6 +30,7 @@ func NewDashboardSearchClient(dashboardStore dashboards.Store) *DashboardSearchC
|
||||
return &DashboardSearchClient{dashboardStore: dashboardStore}
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
@@ -103,6 +105,8 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
}
|
||||
|
||||
for _, field := range req.Options.Fields {
|
||||
vals := field.GetValues()
|
||||
|
||||
switch field.Key {
|
||||
case resource.SEARCH_FIELD_TAGS:
|
||||
query.Tags = field.GetValues()
|
||||
@@ -110,7 +114,6 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
query.DashboardUIDs = field.GetValues()
|
||||
query.DashboardIds = nil
|
||||
case resource.SEARCH_FIELD_FOLDER:
|
||||
vals := field.GetValues()
|
||||
folders := make([]string, len(vals))
|
||||
|
||||
for i, val := range vals {
|
||||
@@ -122,12 +125,28 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
}
|
||||
|
||||
query.FolderUIDs = folders
|
||||
}
|
||||
}
|
||||
case resource.SEARCH_FIELD_REPOSITORY_PATH:
|
||||
// only one value is supported in legacy search
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one repo path query is supported")
|
||||
}
|
||||
query.ProvisionedPath = vals[0]
|
||||
case resource.SEARCH_FIELD_REPOSITORY_NAME:
|
||||
if field.Operator == string(selection.NotIn) {
|
||||
for _, val := range vals {
|
||||
name, _ := dashboard.GetProvisionedFileNameFromMeta(val)
|
||||
query.ProvisionedReposNotIn = append(query.ProvisionedReposNotIn, name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
res, err := c.dashboardStore.FindDashboards(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// only one value is supported in legacy search
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one repo name is supported")
|
||||
}
|
||||
|
||||
query.ProvisionedRepo, _ = dashboard.GetProvisionedFileNameFromMeta(vals[0])
|
||||
}
|
||||
}
|
||||
|
||||
searchFields := resource.StandardSearchFields()
|
||||
@@ -141,6 +160,41 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
},
|
||||
}
|
||||
|
||||
// if we are querying for provisioning information, we need to use a different
|
||||
// legacy sql query, since legacy search does not support this
|
||||
if query.ProvisionedRepo != "" || len(query.ProvisionedReposNotIn) > 0 {
|
||||
var dashes []*dashboards.Dashboard
|
||||
if query.ProvisionedRepo == dashboard.PluginIDRepoName {
|
||||
dashes, err = c.dashboardStore.GetDashboardsByPluginID(ctx, &dashboards.GetDashboardsByPluginIDQuery{
|
||||
PluginID: query.ProvisionedPath,
|
||||
OrgID: user.GetOrgID(),
|
||||
})
|
||||
} else if query.ProvisionedRepo != "" {
|
||||
dashes, err = c.dashboardStore.GetProvisionedDashboardsByName(ctx, query.ProvisionedRepo)
|
||||
} else if len(query.ProvisionedReposNotIn) > 0 {
|
||||
dashes, err = c.dashboardStore.GetOrphanedProvisionedDashboards(ctx, query.ProvisionedReposNotIn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, dashboard := range dashes {
|
||||
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
|
||||
Key: getResourceKey(&dashboards.DashboardSearchProjection{
|
||||
UID: dashboard.UID,
|
||||
}, req.Options.Key.Namespace),
|
||||
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), []byte{}},
|
||||
})
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
res, err := c.dashboardStore.FindDashboards(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hits := formatQueryResult(res)
|
||||
|
||||
for _, dashboard := range hits {
|
||||
@@ -155,6 +209,8 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
|
||||
})
|
||||
}
|
||||
|
||||
list.TotalHits = int64(len(list.Results.Rows))
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
package legacysearcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
)
|
||||
|
||||
func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
mockStore := dashboards.NewFakeDashboardStore(t)
|
||||
client := NewDashboardSearchClient(mockStore)
|
||||
ctx := context.Background()
|
||||
user := &user.SignedInUser{OrgID: 2}
|
||||
ctx = identity.WithRequester(ctx, user)
|
||||
|
||||
dashboardKey := &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: dashboard.DASHBOARD_RESOURCE,
|
||||
}
|
||||
|
||||
t.Run("Should parse results into GRPC", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
SignedInUser: user, // user from context should be used
|
||||
Type: "dash-db", // should set type based off of key
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"},
|
||||
{UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
tags, err := json.Marshal([]string{"term"})
|
||||
require.NoError(t, err)
|
||||
|
||||
emptyTags, err := json.Marshal([]string{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
searchFields := resource.StandardSearchFields()
|
||||
require.Equal(t, &resource.ResourceSearchResponse{
|
||||
TotalHits: 2,
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
||||
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
||||
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Group: dashboard.GROUP,
|
||||
Resource: dashboard.DASHBOARD_RESOURCE,
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Test Dashboard"),
|
||||
[]byte("folder1"),
|
||||
tags,
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid2",
|
||||
Group: dashboard.GROUP,
|
||||
Resource: dashboard.DASHBOARD_RESOURCE,
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Test Dashboard2"),
|
||||
[]byte("folder2"),
|
||||
emptyTags,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Query should be set as the title, and * should be removed", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
Title: "test",
|
||||
SignedInUser: user, // user from context should be used
|
||||
Type: "dash-db", // should set type based off of key
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
},
|
||||
Query: "*test*",
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should read labels for the dashboard ids", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int64{1, 2},
|
||||
SignedInUser: user, // user from context should be used
|
||||
Type: "dash-db", // should set type based off of key
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Labels: []*resource.Requirement{
|
||||
{
|
||||
Key: utils.LabelKeyDeprecatedInternalID,
|
||||
Operator: "in",
|
||||
Values: []string{"1", "2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should modify fields to legacy compatible queries", func(t *testing.T) {
|
||||
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
||||
DashboardUIDs: []string{"uid1", "uid2"},
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
FolderUIDs: []string{"general", "folder1"},
|
||||
SignedInUser: user, // user from context should be used
|
||||
Type: "dash-db", // should set type based off of key
|
||||
}).Return([]dashboards.DashboardSearchProjection{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_TAGS,
|
||||
Operator: "in",
|
||||
Values: []string{"tag1", "tag2"},
|
||||
},
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_NAME, // name should be used as uid
|
||||
Operator: "in",
|
||||
Values: []string{"uid1", "uid2"},
|
||||
},
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_FOLDER,
|
||||
Operator: "in",
|
||||
Values: []string{"", "folder1"}, // empty folder should be general
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should retrieve dashboards by plugin through a different function", func(t *testing.T) {
|
||||
mockStore.On("GetDashboardsByPluginID", mock.Anything, &dashboards.GetDashboardsByPluginIDQuery{
|
||||
PluginID: "slo",
|
||||
OrgID: 2, // retrieved from the signed in user
|
||||
}).Return([]*dashboards.Dashboard{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_PATH,
|
||||
Operator: "in",
|
||||
Values: []string{"slo"},
|
||||
},
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
Operator: "in",
|
||||
Values: []string{"plugin"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should retrieve dashboards by provisioner name through a different function", func(t *testing.T) {
|
||||
mockStore.On("GetProvisionedDashboardsByName", mock.Anything, "test").Return([]*dashboards.Dashboard{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
Operator: "in",
|
||||
Values: []string{"file:test"}, // file prefix should be removed before going to legacy
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Should retrieve orphaned dashboards if provisioner not in is specified", func(t *testing.T) {
|
||||
mockStore.On("GetOrphanedProvisionedDashboards", mock.Anything, []string{"test", "test2"}).Return([]*dashboards.Dashboard{
|
||||
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
||||
}, nil).Once()
|
||||
|
||||
req := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resource.Requirement{
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_REPOSITORY_NAME,
|
||||
Operator: string(selection.NotIn),
|
||||
Values: []string{"file:test", "file:test2"}, // file prefix should be removed before going to legacy
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dashboard
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
@@ -25,7 +26,10 @@ import (
|
||||
folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
foldermodel "github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/util/errhttp"
|
||||
@@ -33,17 +37,19 @@ import (
|
||||
|
||||
// The DTO returns everything the UI needs in a single request
|
||||
type SearchHandler struct {
|
||||
log log.Logger
|
||||
client func(context.Context) resource.ResourceIndexClient
|
||||
tracer trace.Tracer
|
||||
log log.Logger
|
||||
client func(context.Context) resource.ResourceIndexClient
|
||||
tracer trace.Tracer
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func NewSearchHandler(tracer trace.Tracer, cfg *setting.Cfg, legacyDashboardSearcher resource.ResourceIndexClient) *SearchHandler {
|
||||
func NewSearchHandler(tracer trace.Tracer, cfg *setting.Cfg, legacyDashboardSearcher resource.ResourceIndexClient, features featuremgmt.FeatureToggles) *SearchHandler {
|
||||
searchClient := resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, unified.GetResourceClient, legacyDashboardSearcher)
|
||||
return &SearchHandler{
|
||||
client: searchClient,
|
||||
log: log.New("grafana-apiserver.dashboards.search"),
|
||||
tracer: tracer,
|
||||
client: searchClient,
|
||||
log: log.New("grafana-apiserver.dashboards.search"),
|
||||
tracer: tracer,
|
||||
features: features,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,19 +258,6 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
searchRequest.Fields = fields
|
||||
|
||||
// Add the folder constraint. Note this does not do recursive search
|
||||
folder := queryParams.Get("folder")
|
||||
if folder != "" {
|
||||
if folder == rootFolder {
|
||||
folder = "" // root folder is empty in the search index
|
||||
}
|
||||
searchRequest.Options.Fields = []*resource.Requirement{{
|
||||
Key: "folder",
|
||||
Operator: "=",
|
||||
Values: []string{folder},
|
||||
}}
|
||||
}
|
||||
|
||||
types := queryParams["type"]
|
||||
var federate *resource.ResourceKey
|
||||
switch len(types) {
|
||||
@@ -329,7 +322,33 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// The names filter
|
||||
if names, ok := queryParams["name"]; ok {
|
||||
names := queryParams["name"]
|
||||
|
||||
// Add the folder constraint. Note this does not do recursive search
|
||||
folder := queryParams.Get("folder")
|
||||
if folder == foldermodel.SharedWithMeFolderUID {
|
||||
dashboardUIDs, err := s.getDashboardsUIDsSharedWithUser(ctx, user)
|
||||
if err != nil {
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
// hijacks the "name" query param to only search for shared dashboard UIDs
|
||||
if len(dashboardUIDs) > 0 {
|
||||
names = append(names, dashboardUIDs...)
|
||||
}
|
||||
} else if folder != "" {
|
||||
if folder == rootFolder {
|
||||
folder = "" // root folder is empty in the search index
|
||||
}
|
||||
searchRequest.Options.Fields = []*resource.Requirement{{
|
||||
Key: "folder",
|
||||
Operator: "=",
|
||||
Values: []string{folder},
|
||||
}}
|
||||
}
|
||||
|
||||
if len(names) > 0 {
|
||||
if searchRequest.Options.Fields == nil {
|
||||
searchRequest.Options.Fields = []*resource.Requirement{}
|
||||
}
|
||||
@@ -378,3 +397,108 @@ func asResourceKey(ns string, k string) (*resource.ResourceKey, error) {
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester) ([]string, error) {
|
||||
if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// gets dashboards that the user was granted read access to
|
||||
permissions := user.GetPermissions()
|
||||
dashboardPermissions := permissions[dashboards.ActionDashboardsRead]
|
||||
dashboardUids := make([]string, 0)
|
||||
sharedDashboards := make([]string, 0)
|
||||
|
||||
for _, dashboardPermission := range dashboardPermissions {
|
||||
if dashboardUid, found := strings.CutPrefix(dashboardPermission, dashboards.ScopeDashboardsPrefix); found {
|
||||
if !slices.Contains(dashboardUids, dashboardUid) {
|
||||
dashboardUids = append(dashboardUids, dashboardUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(dashboardUids) == 0 {
|
||||
return sharedDashboards, nil
|
||||
}
|
||||
|
||||
key, err := asResourceKey(user.GetNamespace(), dashboard.DASHBOARD_RESOURCE)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
dashboardSearchRequest := &resource.ResourceSearchRequest{
|
||||
Fields: []string{"folder"},
|
||||
Limit: int64(len(dashboardUids)),
|
||||
Options: &resource.ListOptions{
|
||||
Key: key,
|
||||
Fields: []*resource.Requirement{{
|
||||
Key: "name",
|
||||
Operator: "in",
|
||||
Values: dashboardUids,
|
||||
}},
|
||||
},
|
||||
}
|
||||
// get all dashboards user has access to, along with their parent folder uid
|
||||
dashboardResult, err := s.client(ctx).Search(ctx, dashboardSearchRequest)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
folderUidIdx := -1
|
||||
for i, col := range dashboardResult.Results.Columns {
|
||||
if col.Name == "folder" {
|
||||
folderUidIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
if folderUidIdx == -1 {
|
||||
return sharedDashboards, fmt.Errorf("Error retrieving folder information")
|
||||
}
|
||||
|
||||
// populate list of unique folder UIDs in the list of dashboards user has read permissions
|
||||
allFolders := make([]string, 0)
|
||||
for _, dash := range dashboardResult.Results.Rows {
|
||||
folderUid := string(dash.Cells[folderUidIdx])
|
||||
if folderUid != "" && !slices.Contains(allFolders, folderUid) {
|
||||
allFolders = append(allFolders, folderUid)
|
||||
}
|
||||
}
|
||||
|
||||
// only folders the user has access to will be returned here
|
||||
folderKey, err := asResourceKey(user.GetNamespace(), folderv0alpha1.RESOURCE)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
folderSearchRequest := &resource.ResourceSearchRequest{
|
||||
Fields: []string{"folder"},
|
||||
Limit: int64(len(allFolders)),
|
||||
Options: &resource.ListOptions{
|
||||
Key: folderKey,
|
||||
Fields: []*resource.Requirement{{
|
||||
Key: "name",
|
||||
Operator: "in",
|
||||
Values: allFolders,
|
||||
}},
|
||||
},
|
||||
}
|
||||
foldersResult, err := s.client(ctx).Search(ctx, folderSearchRequest)
|
||||
if err != nil {
|
||||
return sharedDashboards, err
|
||||
}
|
||||
|
||||
foldersWithAccess := make([]string, 0, len(foldersResult.Results.Rows))
|
||||
for _, fold := range foldersResult.Results.Rows {
|
||||
foldersWithAccess = append(foldersWithAccess, fold.Key.Name)
|
||||
}
|
||||
|
||||
// add to sharedDashboards dashboards user has access to, but does NOT have access to it's parent folder
|
||||
for _, dash := range dashboardResult.Results.Rows {
|
||||
dashboardUid := dash.Key.Name
|
||||
folderUid := string(dash.Cells[folderUidIdx])
|
||||
if folderUid != "" && !slices.Contains(foldersWithAccess, folderUid) {
|
||||
sharedDashboards = append(sharedDashboards, dashboardUid)
|
||||
}
|
||||
}
|
||||
return sharedDashboards, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
@@ -31,7 +33,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode0},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -59,7 +61,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode1},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -87,7 +89,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode2},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -115,7 +117,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode3},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -143,7 +145,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode4},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -171,7 +173,7 @@ func TestSearchFallback(t *testing.T) {
|
||||
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode5},
|
||||
},
|
||||
}
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient)
|
||||
searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), cfg, mockLegacyClient, nil)
|
||||
searchHandler.client = resource.NewSearchClient(cfg, setting.UnifiedStorageConfigKeyDashboard, mockUnifiedCtxclient, mockLegacyClient)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -191,17 +193,19 @@ func TestSearchFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSearchHandler(t *testing.T) {
|
||||
// Create a mock client
|
||||
mockClient := &MockClient{}
|
||||
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
}
|
||||
|
||||
t.Run("Multiple comma separated fields will be appended to default dashboard search fields", func(t *testing.T) {
|
||||
// Create a mock client
|
||||
mockClient := &MockClient{}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search?field=field1&field=field2&field=field3", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
@@ -219,6 +223,18 @@ func TestSearchHandler(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Single field will be appended to default dashboard search fields", func(t *testing.T) {
|
||||
// Create a mock client
|
||||
mockClient := &MockClient{}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search?field=field1", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
@@ -236,6 +252,18 @@ func TestSearchHandler(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Passing no fields will search using default dashboard fields", func(t *testing.T) {
|
||||
// Create a mock client
|
||||
mockClient := &MockClient{}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
@@ -253,6 +281,41 @@ func TestSearchHandler(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Sort - default sort by resource then title", func(t *testing.T) {
|
||||
rows := make([]*resource.ResourceTableRow, len(mockResults))
|
||||
for i, r := range mockResults {
|
||||
rows[i] = &resource.ResourceTableRow{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: r.Name,
|
||||
Resource: r.Resource,
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte(r.Value),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
mockResponse := &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{Name: resource.SEARCH_FIELD_TITLE},
|
||||
},
|
||||
Rows: rows,
|
||||
},
|
||||
}
|
||||
// Create a mock client
|
||||
mockClient := &MockClient{
|
||||
MockResponses: []*resource.ResourceSearchResponse{mockResponse},
|
||||
}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
// Initialize the search handler with the mock client
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
@@ -280,6 +343,125 @@ func TestSearchHandler(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSearchHandlerSharedDashboards(t *testing.T) {
|
||||
t.Run("should bail out if FlagUnifiedStorageSearchPermissionFiltering is not enabled globally", func(t *testing.T) {
|
||||
mockClient := &MockClient{}
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search?folder=sharedwithme", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"}))
|
||||
|
||||
searchHandler.DoSearch(rr, req)
|
||||
|
||||
assert.Equal(t, mockClient.CallCount, 1)
|
||||
})
|
||||
|
||||
t.Run("should return the dashboards shared with the user", func(t *testing.T) {
|
||||
// dashboardSearchRequest
|
||||
mockResponse1 := &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "dashboardinroot",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{[]byte("")}, // root folder doesn't have uid
|
||||
},
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "dashboardinprivatefolder",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("privatefolder"), // folder uid
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "dashboardinpublicfolder",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("publicfolder"), // folder uid
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// folderSearchRequest
|
||||
mockResponse2 := &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "folder",
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "publicfolder",
|
||||
Resource: "folder",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte(""), // root folder uid
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockClient := &MockClient{
|
||||
MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2},
|
||||
}
|
||||
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering)
|
||||
searchHandler := SearchHandler{
|
||||
log: log.New("test", "test"),
|
||||
client: func(context.Context) resource.ResourceIndexClient { return mockClient },
|
||||
tracer: tracing.NewNoopTracerService(),
|
||||
features: features,
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/search?folder=sharedwithme", nil)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
allPermissions := make(map[int64]map[string][]string)
|
||||
permissions := make(map[string][]string)
|
||||
permissions[dashboards.ActionDashboardsRead] = []string{"dashboards:uid:dashboardinroot", "dashboards:uid:dashboardinprivatefolder", "dashboards:uid:dashboardinpublicfolder"}
|
||||
allPermissions[1] = permissions
|
||||
req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test", OrgID: 1, Permissions: allPermissions}))
|
||||
|
||||
searchHandler.DoSearch(rr, req)
|
||||
|
||||
assert.Equal(t, mockClient.CallCount, 3)
|
||||
|
||||
// first call gets all dashboards user has permission for
|
||||
firstCall := mockClient.MockCalls[0]
|
||||
assert.Equal(t, firstCall.Options.Fields[0].Values, []string{"dashboardinroot", "dashboardinprivatefolder", "dashboardinpublicfolder"})
|
||||
// second call gets folders associated with the previous dashboards
|
||||
secondCall := mockClient.MockCalls[1]
|
||||
assert.Equal(t, secondCall.Options.Fields[0].Values, []string{"privatefolder", "publicfolder"})
|
||||
// lastly, search ONLY for dashboards user has permission to read that are within folders the user does NOT have
|
||||
// permission to read
|
||||
thirdCall := mockClient.MockCalls[2]
|
||||
assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder"})
|
||||
})
|
||||
}
|
||||
|
||||
// MockClient implements the ResourceIndexClient interface for testing
|
||||
type MockClient struct {
|
||||
resource.ResourceIndexClient
|
||||
@@ -287,6 +469,10 @@ type MockClient struct {
|
||||
|
||||
// Capture the last SearchRequest for assertions
|
||||
LastSearchRequest *resource.ResourceSearchRequest
|
||||
|
||||
MockResponses []*resource.ResourceSearchResponse
|
||||
MockCalls []*resource.ResourceSearchRequest
|
||||
CallCount int
|
||||
}
|
||||
|
||||
type MockResult struct {
|
||||
@@ -320,28 +506,16 @@ var mockResults = []MockResult{
|
||||
|
||||
func (m *MockClient) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
|
||||
m.LastSearchRequest = in
|
||||
m.MockCalls = append(m.MockCalls, in)
|
||||
|
||||
rows := make([]*resource.ResourceTableRow, len(mockResults))
|
||||
for i, r := range mockResults {
|
||||
rows[i] = &resource.ResourceTableRow{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: r.Name,
|
||||
Resource: r.Resource,
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte(r.Value),
|
||||
},
|
||||
}
|
||||
var response *resource.ResourceSearchResponse
|
||||
if m.CallCount < len(m.MockResponses) {
|
||||
response = m.MockResponses[m.CallCount]
|
||||
}
|
||||
|
||||
return &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{Name: resource.SEARCH_FIELD_TITLE},
|
||||
},
|
||||
Rows: rows,
|
||||
},
|
||||
}, nil
|
||||
m.CallCount = m.CallCount + 1
|
||||
|
||||
return response, nil
|
||||
}
|
||||
func (m *MockClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
|
||||
return nil, nil
|
||||
|
||||
@@ -125,7 +125,7 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if repo != nil && repo.Name == "plugin" {
|
||||
if repo != nil && repo.Name == dashboard.PluginIDRepoName {
|
||||
dto.PluginID = repo.Path
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
features: features,
|
||||
accessControl: accessControl,
|
||||
unified: unified,
|
||||
search: dashboard.NewSearchHandler(tracing, cfg, legacyDashboardSearcher),
|
||||
search: dashboard.NewSearchHandler(tracing, cfg, legacyDashboardSearcher, features),
|
||||
|
||||
legacy: &dashboard.DashboardStorage{
|
||||
Resource: dashboardv0alpha1.DashboardResourceInfo,
|
||||
|
||||
@@ -81,6 +81,8 @@ type Store interface {
|
||||
GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error)
|
||||
GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error)
|
||||
GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error)
|
||||
GetProvisionedDashboardsByName(ctx context.Context, name string) ([]*Dashboard, error)
|
||||
GetOrphanedProvisionedDashboards(ctx context.Context, notIn []string) ([]*Dashboard, error)
|
||||
SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error)
|
||||
SaveProvisionedDashboard(ctx context.Context, dash *Dashboard, provisioning *DashboardProvisioning) error
|
||||
UnprovisionDashboard(ctx context.Context, id int64) error
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.32.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.2. DO NOT EDIT.
|
||||
|
||||
package dashboards
|
||||
|
||||
@@ -18,6 +18,10 @@ type FakeDashboardProvisioning struct {
|
||||
func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteOrphanedProvisionedDashboards")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
@@ -32,6 +36,10 @@ func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx con
|
||||
func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
|
||||
ret := _m.Called(ctx, dashboardID, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteProvisionedDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
|
||||
r0 = rf(ctx, dashboardID, orgID)
|
||||
@@ -46,6 +54,10 @@ func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Cont
|
||||
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) {
|
||||
ret := _m.Called(ctx, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetProvisionedDashboardData")
|
||||
}
|
||||
|
||||
var r0 []*DashboardProvisioning
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*DashboardProvisioning, error)); ok {
|
||||
@@ -72,6 +84,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Con
|
||||
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) {
|
||||
ret := _m.Called(ctx, dashboardID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetProvisionedDashboardDataByDashboardID")
|
||||
}
|
||||
|
||||
var r0 *DashboardProvisioning
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok {
|
||||
@@ -98,6 +114,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ct
|
||||
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) {
|
||||
ret := _m.Called(ctx, orgID, dashboardUID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetProvisionedDashboardDataByDashboardUID")
|
||||
}
|
||||
|
||||
var r0 *DashboardProvisioning
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok {
|
||||
@@ -124,6 +144,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(c
|
||||
func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveFolderForProvisionedDashboards")
|
||||
}
|
||||
|
||||
var r0 *folder.Folder
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *folder.CreateFolderCommand) (*folder.Folder, error)); ok {
|
||||
@@ -150,6 +174,10 @@ func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 cont
|
||||
func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *DashboardProvisioning) (*Dashboard, error) {
|
||||
ret := _m.Called(ctx, dto, provisioning)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveProvisionedDashboard")
|
||||
}
|
||||
|
||||
var r0 *Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, *DashboardProvisioning) (*Dashboard, error)); ok {
|
||||
@@ -176,6 +204,10 @@ func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Contex
|
||||
func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, dashboardID int64) error {
|
||||
ret := _m.Called(ctx, dashboardID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UnprovisionDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, dashboardID)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.2. DO NOT EDIT.
|
||||
|
||||
package dashboards
|
||||
|
||||
@@ -74,6 +74,34 @@ func (_m *FakeDashboardService) CleanUpDeletedDashboards(ctx context.Context) (i
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountDashboardsInOrg provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CountDashboardsInOrg")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok {
|
||||
return rf(ctx, orgID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok {
|
||||
r0 = rf(ctx, orgID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, orgID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountInFolders provides a mock function with given fields: ctx, orgID, folderUIDs, user
|
||||
func (_m *FakeDashboardService) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) (int64, error) {
|
||||
ret := _m.Called(ctx, orgID, folderUIDs, user)
|
||||
@@ -102,6 +130,24 @@ func (_m *FakeDashboardService) CountInFolders(ctx context.Context, orgID int64,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) DeleteAllDashboards(ctx context.Context, orgID int64) error {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteAllDashboards")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, orgID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteDashboard provides a mock function with given fields: ctx, dashboardId, dashboardUID, orgId
|
||||
func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, dashboardUID string, orgId int64) error {
|
||||
ret := _m.Called(ctx, dashboardId, dashboardUID, orgId)
|
||||
@@ -120,24 +166,6 @@ func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) DeleteAllDashboards(ctx context.Context, orgID int64) error {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteDashboard")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, orgID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// FindDashboards provides a mock function with given fields: ctx, query
|
||||
func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
@@ -168,36 +196,6 @@ func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindP
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountDashboardsInOrg provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CountDashboardsInOrg")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok {
|
||||
return rf(ctx, orgID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok {
|
||||
r0 = rf(ctx, orgID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, orgID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAllDashboards provides a mock function with given fields: ctx
|
||||
func (_m *FakeDashboardService) GetAllDashboards(ctx context.Context) ([]*Dashboard, error) {
|
||||
ret := _m.Called(ctx)
|
||||
@@ -228,6 +226,7 @@ func (_m *FakeDashboardService) GetAllDashboards(ctx context.Context) ([]*Dashbo
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAllDashboardsByOrgId provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardService) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
|
||||
@@ -146,6 +146,38 @@ func (d *dashboardStore) GetProvisionedDashboardData(ctx context.Context, name s
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (d *dashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string) ([]*dashboards.Dashboard, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.GetProvisionedDashboardsByName")
|
||||
defer span.End()
|
||||
|
||||
dashes := []*dashboards.Dashboard{}
|
||||
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.Table(`dashboard`).
|
||||
Join(`INNER`, `dashboard_provisioning`, `dashboard.id = dashboard_provisioning.dashboard_id`).
|
||||
Where(`dashboard_provisioning.name = ?`, name).Find(&dashes)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dashes, nil
|
||||
}
|
||||
|
||||
func (d *dashboardStore) GetOrphanedProvisionedDashboards(ctx context.Context, notIn []string) ([]*dashboards.Dashboard, error) {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.GetOrphanedProvisionedDashboards")
|
||||
defer span.End()
|
||||
|
||||
dashes := []*dashboards.Dashboard{}
|
||||
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
return sess.Table(`dashboard`).
|
||||
Join(`INNER`, `dashboard_provisioning`, `dashboard.id = dashboard_provisioning.dashboard_id`).
|
||||
NotIn(`dashboard_provisioning.name`, notIn).Find(&dashes)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dashes, nil
|
||||
}
|
||||
|
||||
func (d *dashboardStore) SaveProvisionedDashboard(ctx context.Context, dash *dashboards.Dashboard, provisioning *dashboards.DashboardProvisioning) error {
|
||||
ctx, span := tracer.Start(ctx, "dashboards.database.SaveProvisionedDashboard")
|
||||
defer span.End()
|
||||
|
||||
@@ -251,26 +251,51 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
|
||||
t.Run("Should delete associated provisioning info, even without the dashboard existing in the db", func(t *testing.T) {
|
||||
setup()
|
||||
dash1 := insertTestDashboard(t, dashboardStore, "provisioned", 1, 0, "", false, "provisioned")
|
||||
dash2 := insertTestDashboard(t, dashboardStore, "orphaned", 1, 0, "", false, "orphaned")
|
||||
provisioningData := &dashboards.DashboardProvisioning{
|
||||
ID: 1,
|
||||
DashboardID: 200,
|
||||
DashboardID: dash1.ID,
|
||||
Name: "test",
|
||||
CheckSum: "123",
|
||||
Updated: 54321,
|
||||
ExternalID: "/path/to/dashboard",
|
||||
}
|
||||
err := dashboardStore.SaveProvisionedDashboard(context.Background(), &dashboards.Dashboard{
|
||||
ID: 200,
|
||||
}, provisioningData)
|
||||
err := dashboardStore.SaveProvisionedDashboard(context.Background(), dash1, provisioningData)
|
||||
require.NoError(t, err)
|
||||
provisioningData2 := &dashboards.DashboardProvisioning{
|
||||
ID: 1,
|
||||
DashboardID: dash2.ID,
|
||||
Name: "orphaned",
|
||||
CheckSum: "123",
|
||||
Updated: 54321,
|
||||
ExternalID: "/path/to/dashboard",
|
||||
}
|
||||
err = dashboardStore.SaveProvisionedDashboard(context.Background(), dash2, provisioningData2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// get provisioning data
|
||||
res, err := dashboardStore.GetProvisionedDashboardData(context.Background(), "test")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, res[0], provisioningData)
|
||||
|
||||
// get dashboards within the provisioner
|
||||
dashs, err := dashboardStore.GetProvisionedDashboardsByName(context.Background(), "test")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dashs, 1)
|
||||
|
||||
// find dashboards not within that provisioner
|
||||
dashs, err = dashboardStore.GetOrphanedProvisionedDashboards(context.Background(), []string{"test"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dashs, 1)
|
||||
// if both are provided, nothing should be returned
|
||||
dashs, err = dashboardStore.GetOrphanedProvisionedDashboards(context.Background(), []string{"test", "orphaned"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dashs, 0)
|
||||
|
||||
err = dashboardStore.CleanupAfterDelete(context.Background(), &dashboards.DeleteDashboardCommand{
|
||||
ID: 200,
|
||||
ID: dash1.ID,
|
||||
OrgID: 1,
|
||||
UID: "test",
|
||||
})
|
||||
|
||||
@@ -958,7 +958,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
|
||||
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: query.OrgID,
|
||||
ProvisionedRepo: pluginIDRepoName,
|
||||
ProvisionedRepo: dashboard.PluginIDRepoName,
|
||||
ProvisionedPath: query.PluginID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1559,7 +1559,7 @@ func (dr *DashboardServiceImpl) saveProvisionedDashboardThroughK8s(ctx context.C
|
||||
delete(annotations, utils.AnnoKeyRepoHash)
|
||||
delete(annotations, utils.AnnoKeyRepoTimestamp)
|
||||
} else {
|
||||
annotations[utils.AnnoKeyRepoName] = provisionedFileNameWithPrefix(provisioning.Name)
|
||||
annotations[utils.AnnoKeyRepoName] = dashboard.ProvisionedFileNameWithPrefix(provisioning.Name)
|
||||
annotations[utils.AnnoKeyRepoPath] = provisioning.ExternalID
|
||||
annotations[utils.AnnoKeyRepoHash] = provisioning.CheckSum
|
||||
annotations[utils.AnnoKeyRepoTimestamp] = time.Unix(provisioning.Updated, 0).UTC().Format(time.RFC3339)
|
||||
@@ -1580,7 +1580,7 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setPluginID(obj, cmd.PluginID)
|
||||
dashboard.SetPluginIDMeta(obj, cmd.PluginID)
|
||||
|
||||
out, err := dr.createOrUpdateDash(ctx, obj, orgID)
|
||||
if err != nil {
|
||||
@@ -1827,13 +1827,13 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
ctx, _ = identity.WithServiceIdentity(ctx, query.OrgId)
|
||||
|
||||
if query.ProvisionedRepo != "" {
|
||||
query.ProvisionedRepo = provisionedFileNameWithPrefix(query.ProvisionedRepo)
|
||||
query.ProvisionedRepo = dashboard.ProvisionedFileNameWithPrefix(query.ProvisionedRepo)
|
||||
}
|
||||
|
||||
if len(query.ProvisionedReposNotIn) > 0 {
|
||||
repos := make([]string, len(query.ProvisionedReposNotIn))
|
||||
for i, v := range query.ProvisionedReposNotIn {
|
||||
repos[i] = provisionedFileNameWithPrefix(v)
|
||||
repos[i] = dashboard.ProvisionedFileNameWithPrefix(v)
|
||||
}
|
||||
query.ProvisionedReposNotIn = repos
|
||||
}
|
||||
@@ -1865,7 +1865,7 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
}
|
||||
|
||||
// ensure the repo is set due to file provisioning, otherwise skip it
|
||||
fileRepo, found := getProvisionedFileNameFromMeta(meta)
|
||||
fileRepo, found := dashboard.GetProvisionedFileNameFromMeta(meta.GetRepositoryName())
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
@@ -1967,7 +1967,7 @@ func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Contex
|
||||
out.Deleted = obj.GetDeletionTimestamp().Time
|
||||
}
|
||||
|
||||
out.PluginID = GetPluginIDFromMeta(obj)
|
||||
out.PluginID = dashboard.GetPluginIDFromMeta(obj)
|
||||
|
||||
creator, err := dr.k8sclient.GetUserFromMeta(ctx, obj.GetCreatedBy())
|
||||
if err != nil {
|
||||
@@ -2012,42 +2012,6 @@ func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Contex
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
var pluginIDRepoName = "plugin"
|
||||
var fileProvisionedRepoPrefix = "file:"
|
||||
|
||||
func setPluginID(obj unstructured.Unstructured, pluginID string) {
|
||||
if pluginID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[utils.AnnoKeyRepoName] = pluginIDRepoName
|
||||
annotations[utils.AnnoKeyRepoPath] = pluginID
|
||||
obj.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
func provisionedFileNameWithPrefix(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fileProvisionedRepoPrefix + name
|
||||
}
|
||||
|
||||
func getProvisionedFileNameFromMeta(obj utils.GrafanaMetaAccessor) (string, bool) {
|
||||
return strings.CutPrefix(obj.GetRepositoryName(), fileProvisionedRepoPrefix)
|
||||
}
|
||||
|
||||
func GetPluginIDFromMeta(obj utils.GrafanaMetaAccessor) string {
|
||||
if obj.GetRepositoryName() == pluginIDRepoName {
|
||||
return obj.GetRepositoryPath()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, namespace string) (unstructured.Unstructured, error) {
|
||||
uid := cmd.GetDashboardModel().UID
|
||||
if uid == "" {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
@@ -547,7 +548,7 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -563,7 +564,7 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
k8sCliMock.On("Search", mock.Anything, int64(1),
|
||||
mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix(repo)
|
||||
return req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix(repo)
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{},
|
||||
@@ -573,7 +574,7 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
}, nil).Once()
|
||||
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
// ensure the prefix is added to the query
|
||||
return req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix(repo)
|
||||
return req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix(repo)
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -646,7 +647,7 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -736,7 +737,7 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck
|
||||
},
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -825,7 +826,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "orphaned",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("orphaned"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -838,7 +839,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid2",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: "plugin",
|
||||
utils.AnnoKeyRepoName: dashboard.PluginIDRepoName,
|
||||
utils.AnnoKeyRepoHash: "app",
|
||||
},
|
||||
},
|
||||
@@ -849,7 +850,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid3",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "orphaned",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("orphaned"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -858,7 +859,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
"spec": map[string]any{},
|
||||
}}, nil).Once()
|
||||
k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -888,7 +889,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
}, nil).Once()
|
||||
|
||||
k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == provisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.ProvisionedFileNameWithPrefix("test") && req.Options.Fields[0].Operator == "notin"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -959,7 +960,7 @@ func TestUnprovisionDashboard(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
@@ -1053,7 +1054,7 @@ func TestGetDashboardsByPluginID(t *testing.T) {
|
||||
k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil)
|
||||
k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil)
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == "plugin" &&
|
||||
return req.Options.Fields[0].Key == "repo.name" && req.Options.Fields[0].Values[0] == dashboard.PluginIDRepoName &&
|
||||
req.Options.Fields[1].Key == "repo.path" && req.Options.Fields[1].Values[0] == "testing"
|
||||
})).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
@@ -1983,7 +1984,7 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
"annotations": map[string]any{
|
||||
utils.AnnoKeyRepoName: fileProvisionedRepoPrefix + "test",
|
||||
utils.AnnoKeyRepoName: dashboard.ProvisionedFileNameWithPrefix("test"),
|
||||
utils.AnnoKeyRepoHash: "hash",
|
||||
utils.AnnoKeyRepoPath: "path/to/file",
|
||||
utils.AnnoKeyRepoTimestamp: "2025-01-01T00:00:00Z",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.52.2. DO NOT EDIT.
|
||||
|
||||
package dashboards
|
||||
|
||||
@@ -66,36 +66,6 @@ func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParamet
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountInOrg provides a mock function with given fields: _a0, _a1
|
||||
func (_m *FakeDashboardStore) CountInOrg(_a0 context.Context, _a1 int64) (int64, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Count")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountDashboardsInFolders provides a mock function with given fields: ctx, request
|
||||
func (_m *FakeDashboardStore) CountDashboardsInFolders(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) {
|
||||
ret := _m.Called(ctx, request)
|
||||
@@ -124,6 +94,35 @@ func (_m *FakeDashboardStore) CountDashboardsInFolders(ctx context.Context, requ
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CountInOrg provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64) (int64, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Count")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok {
|
||||
return rf(ctx, orgID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok {
|
||||
r0 = rf(ctx, orgID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, orgID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
|
||||
func (_m *FakeDashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
@@ -436,6 +435,36 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetOrphanedProvisionedDashboards provides a mock function with given fields: ctx, notIn
|
||||
func (_m *FakeDashboardStore) GetOrphanedProvisionedDashboards(ctx context.Context, notIn []string) ([]*Dashboard, error) {
|
||||
ret := _m.Called(ctx, notIn)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetOrphanedProvisionedDashboards")
|
||||
}
|
||||
|
||||
var r0 []*Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*Dashboard, error)); ok {
|
||||
return rf(ctx, notIn)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []string) []*Dashboard); ok {
|
||||
r0 = rf(ctx, notIn)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok {
|
||||
r1 = rf(ctx, notIn)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetProvisionedDashboardData provides a mock function with given fields: ctx, name
|
||||
func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) {
|
||||
ret := _m.Called(ctx, name)
|
||||
@@ -466,6 +495,36 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, n
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetProvisionedDashboardsByName provides a mock function with given fields: ctx, name
|
||||
func (_m *FakeDashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string) ([]*Dashboard, error) {
|
||||
ret := _m.Called(ctx, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetProvisionedDashboardsByName")
|
||||
}
|
||||
|
||||
var r0 []*Dashboard
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*Dashboard, error)); ok {
|
||||
return rf(ctx, name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []*Dashboard); ok {
|
||||
r0 = rf(ctx, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*Dashboard)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetProvisionedDataByDashboardID provides a mock function with given fields: ctx, dashboardID
|
||||
func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) {
|
||||
ret := _m.Called(ctx, dashboardID)
|
||||
|
||||
@@ -194,17 +194,34 @@ export const generateColumns = (
|
||||
if (!info && p === 'general') {
|
||||
info = { kind: 'folder', url: '/dashboards', name: 'Dashboards' };
|
||||
}
|
||||
return info ? (
|
||||
<a key={p} href={info.url} className={styles.locationItem}>
|
||||
<Icon name={getIconForKind(info.kind)} />
|
||||
|
||||
<Text variant="body" truncate>
|
||||
{info.name}
|
||||
</Text>
|
||||
</a>
|
||||
) : (
|
||||
<span key={p}>{p}</span>
|
||||
);
|
||||
if (info) {
|
||||
const content = (
|
||||
<>
|
||||
<Icon name={getIconForKind(info.kind)} />
|
||||
|
||||
<Text variant="body" truncate>
|
||||
{info.name}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
if (info.url) {
|
||||
return (
|
||||
<a key={p} href={info.url} className={styles.locationItem}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={p} className={styles.locationItem}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <span key={p}>{p}</span>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -115,9 +115,6 @@ describe('Unified Storage Searcher', () => {
|
||||
.mockResolvedValueOnce(mockResults)
|
||||
.mockResolvedValueOnce(mockFolders);
|
||||
|
||||
const consoleWarn = jest.fn();
|
||||
jest.spyOn(console, 'warn').mockImplementationOnce(consoleWarn);
|
||||
|
||||
const query: SearchQuery = {
|
||||
query: 'test',
|
||||
limit: 50,
|
||||
@@ -127,14 +124,15 @@ describe('Unified Storage Searcher', () => {
|
||||
|
||||
const response = await searcher.search(query);
|
||||
|
||||
expect(response.view.length).toBe(1);
|
||||
expect(response.view.get(0).title).toBe('DB 2');
|
||||
expect(response.view.length).toBe(2);
|
||||
expect(response.view.get(0).title).toBe('DB 1');
|
||||
expect(response.view.get(0).folder).toBe('sharedwithme');
|
||||
expect(response.view.get(1).title).toBe('DB 2');
|
||||
|
||||
const df = response.view.dataFrame;
|
||||
const locationInfo = df.meta?.custom?.locationInfo;
|
||||
expect(locationInfo).toBeDefined();
|
||||
expect(locationInfo?.folder2.name).toBe('Folder 2');
|
||||
expect(consoleWarn).toHaveBeenCalled();
|
||||
expect(mockSearcher.search).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
|
||||
@@ -204,15 +204,19 @@ export class UnifiedSearcher implements GrafanaSearcher {
|
||||
if (!hasMissing) {
|
||||
return rsp;
|
||||
}
|
||||
// we still have results here with folders we can't find
|
||||
// filter the results since we probably don't have access to that folder
|
||||
|
||||
const locationInfo = await this.locationInfo;
|
||||
const hits = rsp.hits.filter((hit) => {
|
||||
if (hit.folder === undefined || locationInfo[hit.folder] !== undefined) {
|
||||
return true;
|
||||
const hits = rsp.hits.map((hit) => {
|
||||
if (hit.folder === undefined) {
|
||||
return { ...hit, location: 'general', folder: 'general' };
|
||||
}
|
||||
console.warn('Dropping search hit with missing folder', hit);
|
||||
return false;
|
||||
|
||||
// this means user has permission to see this dashboard, but not the folder contents
|
||||
if (locationInfo[hit.folder] === undefined) {
|
||||
return { ...hit, location: 'sharedwithme', folder: 'sharedwithme' };
|
||||
}
|
||||
|
||||
return hit;
|
||||
});
|
||||
const totalHits = rsp.totalHits - (rsp.hits.length - hits.length);
|
||||
return { ...rsp, hits, totalHits };
|
||||
@@ -370,6 +374,11 @@ async function loadLocationInfo(): Promise<Record<string, LocationInfo>> {
|
||||
name: 'Dashboards',
|
||||
url: '/dashboards',
|
||||
}, // share location info with everyone
|
||||
sharedwithme: {
|
||||
kind: 'sharedwithme',
|
||||
name: 'Shared with me',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
for (const hit of rsp.hits) {
|
||||
locationInfo[hit.name] = {
|
||||
|
||||
@@ -49,6 +49,10 @@ export function getIconForKind(kind: string, isOpen?: boolean): IconName {
|
||||
return isOpen ? 'folder-open' : 'folder';
|
||||
}
|
||||
|
||||
if (kind === 'sharedwithme') {
|
||||
return 'users-alt';
|
||||
}
|
||||
|
||||
return 'question-circle';
|
||||
}
|
||||
|
||||
|
||||
@@ -650,10 +650,9 @@ function getVariableSet(
|
||||
addFilterButtonText: 'Select resource attributes',
|
||||
datasource: trailDS,
|
||||
hide: VariableHide.hideVariable,
|
||||
layout: 'vertical',
|
||||
layout: 'combobox',
|
||||
defaultKeys: [],
|
||||
applyMode: 'manual',
|
||||
supportsMultiValueOperators: true,
|
||||
allowCustomValue: true,
|
||||
}),
|
||||
new AdHocFiltersVariable({
|
||||
@@ -662,12 +661,10 @@ function getVariableSet(
|
||||
datasource: trailDS,
|
||||
// default to use var filters and have otel off
|
||||
hide: VariableHide.hideLabel,
|
||||
layout: 'vertical',
|
||||
layout: 'combobox',
|
||||
filters: initialFilters ?? [],
|
||||
baseFilters: getBaseFiltersForMetric(metric),
|
||||
applyMode: 'manual',
|
||||
// since we only support prometheus datasources, this is always true
|
||||
supportsMultiValueOperators: true,
|
||||
allowCustomValue: true,
|
||||
expressionBuilder: (filters: AdHocVariableFilter[]) => {
|
||||
return [...getBaseFiltersForMetric(metric), ...filters]
|
||||
@@ -691,12 +688,10 @@ function getVariableSet(
|
||||
addFilterButtonText: 'Filter',
|
||||
datasource: trailDS,
|
||||
hide: VariableHide.hideVariable,
|
||||
layout: 'vertical',
|
||||
layout: 'combobox',
|
||||
filters: initialFilters ?? [],
|
||||
baseFilters: getBaseFiltersForMetric(metric),
|
||||
applyMode: 'manual',
|
||||
// since we only support prometheus datasources, this is always true
|
||||
supportsMultiValueOperators: true,
|
||||
allowCustomValue: true,
|
||||
// skipUrlSync: true
|
||||
}),
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"save-button": ""
|
||||
}
|
||||
},
|
||||
"actions-editor": {
|
||||
"inline": {
|
||||
"add-button": "",
|
||||
"one-click-link": ""
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"anon-users": {
|
||||
"not-found": ""
|
||||
@@ -174,6 +168,24 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alert": {
|
||||
"alert-state": "",
|
||||
"annotations": "",
|
||||
"evaluation": "",
|
||||
"evaluation-paused": "",
|
||||
"evaluation-paused-description": "",
|
||||
"last-evaluated": "",
|
||||
"last-evaluation-duration": "",
|
||||
"last-updated-at": "",
|
||||
"last-updated-by": "",
|
||||
"no-annotations": "",
|
||||
"pending-period": "",
|
||||
"rule": "",
|
||||
"rule-identifier": "",
|
||||
"rule-type": "",
|
||||
"state-error-timeout": "",
|
||||
"state-no-data": ""
|
||||
},
|
||||
"alert-recording-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@@ -283,6 +295,7 @@
|
||||
"contactPointFilter": {
|
||||
"label": ""
|
||||
},
|
||||
"copy-to-clipboard": "",
|
||||
"export": {
|
||||
"subtitle": {
|
||||
"formats": "",
|
||||
@@ -299,6 +312,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"group-actions": {
|
||||
"actions-trigger": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"export": "",
|
||||
"reorder": ""
|
||||
},
|
||||
"list-view": {
|
||||
"empty": {
|
||||
"new-alert-rule": "",
|
||||
@@ -484,11 +504,23 @@
|
||||
},
|
||||
"rule-list": {
|
||||
"configure-datasource": "",
|
||||
"ds-error-boundary": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
},
|
||||
"filter-view": {
|
||||
"no-more-results": "",
|
||||
"no-rules-found": ""
|
||||
},
|
||||
"new-alert-rule": ""
|
||||
"new-alert-rule": "",
|
||||
"pagination": {
|
||||
"next-page": "",
|
||||
"previous-page": ""
|
||||
},
|
||||
"return-button": {
|
||||
"title": ""
|
||||
},
|
||||
"rulerrule-loading-error": ""
|
||||
},
|
||||
"rule-state": {
|
||||
"creating": "",
|
||||
@@ -690,7 +722,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"custom-value": {
|
||||
"label": ""
|
||||
"description": ""
|
||||
},
|
||||
"group": {
|
||||
"undefined": ""
|
||||
},
|
||||
"options": {
|
||||
"no-found": ""
|
||||
@@ -863,6 +898,82 @@
|
||||
"redirect-link": "Liste in Grafana Alerting",
|
||||
"subtitle": "Benachrichtigungsregeln im Zusammenhang mit diesem Dashboard"
|
||||
},
|
||||
"default-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"repeat": {
|
||||
"direction": {
|
||||
"horizontal": "",
|
||||
"title": "",
|
||||
"vertical": ""
|
||||
},
|
||||
"max": "",
|
||||
"title": "",
|
||||
"variable": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"row-actions": {
|
||||
"delete": "",
|
||||
"modal": {
|
||||
"alt-action": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"button": {
|
||||
"label": ""
|
||||
},
|
||||
"form": {
|
||||
"cancel": "",
|
||||
"repeat-for": {
|
||||
"label": "",
|
||||
"learn-more": "",
|
||||
"warning": {
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-pane": {
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"open": "",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "",
|
||||
"title": ""
|
||||
},
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"add-library-panel-body": "Visualisierungen hinzufügen, die mit anderen Dashboards geteilt werden.",
|
||||
"add-library-panel-button": "Bibliotheksfenster hinzufügen",
|
||||
@@ -874,6 +985,9 @@
|
||||
"import-a-dashboard-header": "Dashboard importieren",
|
||||
"import-dashboard-button": "Dashboard importieren"
|
||||
},
|
||||
"errors": {
|
||||
"failed-to-load": ""
|
||||
},
|
||||
"inspect": {
|
||||
"data-tab": "Daten",
|
||||
"error-tab": "Fehler",
|
||||
@@ -926,19 +1040,130 @@
|
||||
"rows": "Gesamtanzahl an Zeilen",
|
||||
"table-title": "Statistiken"
|
||||
},
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
},
|
||||
"panel-edit": {
|
||||
"alerting-tab": {
|
||||
"dashboard-not-saved": "",
|
||||
"no-rules": ""
|
||||
}
|
||||
},
|
||||
"responsive-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"hide-no-data": "",
|
||||
"title": ""
|
||||
},
|
||||
"name": "",
|
||||
"options": {
|
||||
"columns": "",
|
||||
"fixed": "",
|
||||
"min": "",
|
||||
"one-column": "",
|
||||
"rows": "",
|
||||
"three-columns": "",
|
||||
"two-columns": ""
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"row": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"new": "",
|
||||
"repeat": {
|
||||
"learn-more": "",
|
||||
"warning": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"height": {
|
||||
"expand": "",
|
||||
"hide-row-header": "",
|
||||
"min": "",
|
||||
"title": ""
|
||||
},
|
||||
"repeat": {
|
||||
"title": "",
|
||||
"variable": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"tabs-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"tab": {
|
||||
"new": ""
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Hinzufügen",
|
||||
"add-panel": "",
|
||||
"add-panel-lib": "",
|
||||
"add-row": "",
|
||||
"add-tab": "",
|
||||
"alert-rules": "Warnregeln",
|
||||
"back-to-dashboard": "",
|
||||
"dashboard-settings": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"discard-library-panel-changes": "",
|
||||
"discard-panel": "",
|
||||
"discard-panel-new": "",
|
||||
"edit": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"edit-dashboard-v2-schema": "",
|
||||
"enter-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"exit-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"mark-favorite": "Als Favorit markieren",
|
||||
"more-save-options": "",
|
||||
"open-original": "Original-Dashboard öffnen",
|
||||
"playlist-next": "Zum nächsten Dashboard",
|
||||
"playlist-previous": "Zum vorherigen Dashboard",
|
||||
"playlist-stop": "Wiedergabeliste stoppen",
|
||||
"public-dashboard": "",
|
||||
"refresh": "Dashboard aktualisieren",
|
||||
"save": "Dashboard speichern",
|
||||
"save-dashboard": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-copy": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-short": "",
|
||||
"save-library-panel": "",
|
||||
"settings": "Dashboard-Einstellungen",
|
||||
"share": "Dashboard oder Panel teilen",
|
||||
"share": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"share-button": "Teilen",
|
||||
"show-hidden-elements": "",
|
||||
"switch-old-dashboard": "",
|
||||
"unlink-library-panel": "",
|
||||
"unmark-favorite": "Markierung als Favorit entfernen"
|
||||
},
|
||||
"validation": {
|
||||
@@ -946,6 +1171,14 @@
|
||||
"invalid-json": "JSON ungültig",
|
||||
"tags-expected-array": "Array der jeweiligen Tags",
|
||||
"tags-expected-strings": "String-Array der jeweiligen Tags"
|
||||
},
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": "",
|
||||
"transparent-background": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard-import": {
|
||||
@@ -1048,6 +1281,12 @@
|
||||
"angular-deprecation-description": "",
|
||||
"angular-deprecation-heading": ""
|
||||
},
|
||||
"panel-queries": {
|
||||
"add-query-from-library": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-query-button": ""
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
@@ -1125,40 +1364,11 @@
|
||||
"scan-for-older-logs": "",
|
||||
"stop-scan": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-edit-description": "",
|
||||
"cancel": "",
|
||||
"default-description": "",
|
||||
"delete-query": "",
|
||||
"delete-query-text": "",
|
||||
"delete-query-title": "",
|
||||
"private": "",
|
||||
"public": "",
|
||||
"query-deleted": "",
|
||||
"query-template-add-error": "",
|
||||
"query-template-added": "",
|
||||
"query-template-edit-error": "",
|
||||
"query-template-edited": "",
|
||||
"save": ""
|
||||
},
|
||||
"query-template-modal": {
|
||||
"add-info": "",
|
||||
"add-title": "",
|
||||
"auto-star": "",
|
||||
"data-source-name": "",
|
||||
"description": "",
|
||||
"edit-info": "",
|
||||
"edit-title": "",
|
||||
"query": "",
|
||||
"visibility": ""
|
||||
},
|
||||
"query-template-modall": {
|
||||
"data-source-type": ""
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Abfrageverlauf schließen",
|
||||
"datasource-a-z": "Datenquelle A-Z",
|
||||
"datasource-z-a": "Datenquelle Z-A",
|
||||
"library-history-dropdown": "",
|
||||
"newest-first": "Neueste zuerst",
|
||||
"oldest-first": "Älteste zuerst",
|
||||
"query-history": "Abfrageverlauf",
|
||||
@@ -1305,10 +1515,9 @@
|
||||
"sortBy": ""
|
||||
},
|
||||
"related-logs": {
|
||||
"docsLink": "",
|
||||
"LrrDocsLink": "",
|
||||
"openExploreLogs": "",
|
||||
"relatedLogsUnavailableAfterDocsLink": "",
|
||||
"relatedLogsUnavailableBeforeDocsLink": "",
|
||||
"relatedLogsUnavailable": "",
|
||||
"warnExperimentalFeature": ""
|
||||
},
|
||||
"viewBy": ""
|
||||
@@ -1357,6 +1566,24 @@
|
||||
"send-custom-feedback": ""
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "",
|
||||
"confirm-action": ""
|
||||
},
|
||||
"inline": {
|
||||
"add-action": "",
|
||||
"edit-action": ""
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "",
|
||||
"action-method": "",
|
||||
"action-query-params": "",
|
||||
"action-title": "",
|
||||
"action-title-placeholder": "",
|
||||
"one-click-description": ""
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "",
|
||||
"saving": ""
|
||||
@@ -1373,11 +1600,21 @@
|
||||
},
|
||||
"data-link-editor-modal": {
|
||||
"cancel": "",
|
||||
"one-click-description": "",
|
||||
"save": ""
|
||||
},
|
||||
"data-link-inline-editor": {
|
||||
"one-click": ""
|
||||
},
|
||||
"data-links-inline-editor": {
|
||||
"add-link": "",
|
||||
"one-click-link": ""
|
||||
"edit-link": "",
|
||||
"one-click": "",
|
||||
"one-click-enabled": "",
|
||||
"title-not-provided": "",
|
||||
"tooltip-edit": "",
|
||||
"tooltip-remove": "",
|
||||
"url-not-provided": ""
|
||||
},
|
||||
"data-source-http-settings": {
|
||||
"access-help": "",
|
||||
@@ -1458,7 +1695,12 @@
|
||||
"right-axis-indicator": ""
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"footer-add-annotation": ""
|
||||
"actions-confirmation-input-placeholder": "",
|
||||
"actions-confirmation-label": "",
|
||||
"actions-confirmation-message": "",
|
||||
"footer-add-annotation": "",
|
||||
"footer-click-to-action": "",
|
||||
"footer-click-to-navigate": ""
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
@@ -1780,7 +2022,8 @@
|
||||
},
|
||||
"log-row-message": {
|
||||
"ellipsis": "",
|
||||
"more": ""
|
||||
"more": "",
|
||||
"see-details": ""
|
||||
},
|
||||
"log-rows": {
|
||||
"disable-popover": {
|
||||
@@ -1792,6 +2035,13 @@
|
||||
"shortcut": ""
|
||||
}
|
||||
},
|
||||
"logs-navigation": {
|
||||
"newer-logs": "",
|
||||
"older-logs": "",
|
||||
"scroll-bottom": "",
|
||||
"scroll-top": "",
|
||||
"start-of-range": ""
|
||||
},
|
||||
"popover-menu": {
|
||||
"copy": "",
|
||||
"disable-menu": "",
|
||||
@@ -1881,6 +2131,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rule_groups": "",
|
||||
"alert_rules": "",
|
||||
"contact_points": "",
|
||||
"dashboards": "",
|
||||
@@ -1941,9 +2192,8 @@
|
||||
"public-preview": {
|
||||
"button-text": "",
|
||||
"message": "",
|
||||
"message-plugins": "",
|
||||
"title": "",
|
||||
"title-plugins": ""
|
||||
"message-cloud": "",
|
||||
"title": ""
|
||||
},
|
||||
"resource-details": {
|
||||
"dismiss-button": "",
|
||||
@@ -1986,6 +2236,7 @@
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "",
|
||||
"alert_rule_group": "",
|
||||
"contact_point": "",
|
||||
"dashboard": "Dashboard",
|
||||
"datasource": "Datenquelle",
|
||||
@@ -2031,6 +2282,15 @@
|
||||
"title": "Warum mit Grafana hosten?"
|
||||
}
|
||||
},
|
||||
"multicombobox": {
|
||||
"all": {
|
||||
"title": "",
|
||||
"title-filtered": ""
|
||||
},
|
||||
"clear": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"add-new-connections": {
|
||||
"title": "Neue Verbindung hinzufügen"
|
||||
@@ -2384,7 +2644,8 @@
|
||||
"list-label": "Navigation",
|
||||
"open": "",
|
||||
"undock": "Menü abdocken"
|
||||
}
|
||||
},
|
||||
"rss-button": ""
|
||||
},
|
||||
"news": {
|
||||
"drawer": {
|
||||
@@ -2516,19 +2777,35 @@
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"connections-tab": {
|
||||
"description": ""
|
||||
},
|
||||
"labels": {
|
||||
"contactGrafanaLabs": "",
|
||||
"customLinks": "",
|
||||
"customLinksTooltip": "",
|
||||
"dependencies": "",
|
||||
"documentation": "",
|
||||
"downloads": "",
|
||||
"from": "",
|
||||
"installedVersion": "",
|
||||
"lastCommitDate": "",
|
||||
"latestVersion": "",
|
||||
"links": "",
|
||||
"license": "",
|
||||
"raiseAnIssue": "",
|
||||
"reportAbuse": "",
|
||||
"reportAbuseTooltip": "",
|
||||
"repository": "",
|
||||
"signature": "",
|
||||
"status": "",
|
||||
"updatedAt": ""
|
||||
},
|
||||
"modal": {
|
||||
"cancel": "",
|
||||
"copyEmail": "",
|
||||
"description": "",
|
||||
"node": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"empty-state": {
|
||||
@@ -2743,14 +3020,6 @@
|
||||
"role-label": "Rolle"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"datasource-names": "",
|
||||
"delete-query-button": "",
|
||||
"query-template-get-error": "",
|
||||
"search": "",
|
||||
"user-info-get-error": "",
|
||||
"user-names": ""
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Abfragezeile einklappen",
|
||||
@@ -2760,7 +3029,6 @@
|
||||
"expand-row": "Suchzeile erweitern",
|
||||
"hide-response": "",
|
||||
"remove-query": "Abfrage entfernen",
|
||||
"save-to-query-library": "",
|
||||
"show-response": "",
|
||||
"toggle-edit-mode": "Textbearbeitungsmodus umschalten"
|
||||
},
|
||||
@@ -2862,6 +3130,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"save-dashboards": {
|
||||
"message-length": {
|
||||
"info": "",
|
||||
"title": ""
|
||||
},
|
||||
"name-exists": {
|
||||
"message-info": "",
|
||||
"message-suggestion": "",
|
||||
@@ -3145,6 +3417,7 @@
|
||||
"home-dashboard-placeholder": "Standard-Dashboard",
|
||||
"locale-label": "Sprache",
|
||||
"locale-placeholder": "Sprache wählen",
|
||||
"theme-description": "",
|
||||
"theme-label": "UI-Design",
|
||||
"week-start-label": "Wochenbeginn"
|
||||
},
|
||||
@@ -3216,6 +3489,13 @@
|
||||
"url-column-header": "Snapshot url",
|
||||
"view-button": "Anzeigen"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"content": "",
|
||||
"show-all-series": "",
|
||||
"show-only-series": ""
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "",
|
||||
"loading": "Wird geladen ...",
|
||||
@@ -3309,20 +3589,26 @@
|
||||
"start-your-metrics-exploration": "",
|
||||
"subtitle": ""
|
||||
},
|
||||
"metric-overview": {
|
||||
"description-label": "",
|
||||
"labels": "",
|
||||
"metric-attributes": "",
|
||||
"no-description": "",
|
||||
"type-label": "",
|
||||
"unit-label": "",
|
||||
"unknown-type": ""
|
||||
},
|
||||
"metric-select": {
|
||||
"filter-by": "",
|
||||
"native-histogram": "",
|
||||
"new-badge": "",
|
||||
"otel-switch": ""
|
||||
},
|
||||
"native-histogram-banner": {
|
||||
"ch-heatmap": "",
|
||||
"ch-histogram": "",
|
||||
"click-histogram": "",
|
||||
"hide-examples": "",
|
||||
"learn-more": "",
|
||||
"metric-examples": "",
|
||||
"nh-heatmap": "",
|
||||
"nh-histogram": "",
|
||||
"now": "",
|
||||
"previously": "",
|
||||
"see-examples": "",
|
||||
"sentence": ""
|
||||
},
|
||||
"recent-metrics": {
|
||||
"or-view-a-recent-exploration": ""
|
||||
},
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"save-button": ""
|
||||
}
|
||||
},
|
||||
"actions-editor": {
|
||||
"inline": {
|
||||
"add-button": "",
|
||||
"one-click-link": ""
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"anon-users": {
|
||||
"not-found": ""
|
||||
@@ -174,6 +168,24 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alert": {
|
||||
"alert-state": "",
|
||||
"annotations": "",
|
||||
"evaluation": "",
|
||||
"evaluation-paused": "",
|
||||
"evaluation-paused-description": "",
|
||||
"last-evaluated": "",
|
||||
"last-evaluation-duration": "",
|
||||
"last-updated-at": "",
|
||||
"last-updated-by": "",
|
||||
"no-annotations": "",
|
||||
"pending-period": "",
|
||||
"rule": "",
|
||||
"rule-identifier": "",
|
||||
"rule-type": "",
|
||||
"state-error-timeout": "",
|
||||
"state-no-data": ""
|
||||
},
|
||||
"alert-recording-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@@ -283,6 +295,7 @@
|
||||
"contactPointFilter": {
|
||||
"label": ""
|
||||
},
|
||||
"copy-to-clipboard": "",
|
||||
"export": {
|
||||
"subtitle": {
|
||||
"formats": "",
|
||||
@@ -299,6 +312,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"group-actions": {
|
||||
"actions-trigger": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"export": "",
|
||||
"reorder": ""
|
||||
},
|
||||
"list-view": {
|
||||
"empty": {
|
||||
"new-alert-rule": "",
|
||||
@@ -484,11 +504,23 @@
|
||||
},
|
||||
"rule-list": {
|
||||
"configure-datasource": "",
|
||||
"ds-error-boundary": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
},
|
||||
"filter-view": {
|
||||
"no-more-results": "",
|
||||
"no-rules-found": ""
|
||||
},
|
||||
"new-alert-rule": ""
|
||||
"new-alert-rule": "",
|
||||
"pagination": {
|
||||
"next-page": "",
|
||||
"previous-page": ""
|
||||
},
|
||||
"return-button": {
|
||||
"title": ""
|
||||
},
|
||||
"rulerrule-loading-error": ""
|
||||
},
|
||||
"rule-state": {
|
||||
"creating": "",
|
||||
@@ -690,7 +722,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"custom-value": {
|
||||
"label": ""
|
||||
"description": ""
|
||||
},
|
||||
"group": {
|
||||
"undefined": ""
|
||||
},
|
||||
"options": {
|
||||
"no-found": ""
|
||||
@@ -863,6 +898,82 @@
|
||||
"redirect-link": "Lista en Alertas de Grafana",
|
||||
"subtitle": "Reglas de alerta relacionadas con este tablero"
|
||||
},
|
||||
"default-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"repeat": {
|
||||
"direction": {
|
||||
"horizontal": "",
|
||||
"title": "",
|
||||
"vertical": ""
|
||||
},
|
||||
"max": "",
|
||||
"title": "",
|
||||
"variable": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"row-actions": {
|
||||
"delete": "",
|
||||
"modal": {
|
||||
"alt-action": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"button": {
|
||||
"label": ""
|
||||
},
|
||||
"form": {
|
||||
"cancel": "",
|
||||
"repeat-for": {
|
||||
"label": "",
|
||||
"learn-more": "",
|
||||
"warning": {
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-pane": {
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"open": "",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "",
|
||||
"title": ""
|
||||
},
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"add-library-panel-body": "Añadir las visualizaciones que se comparten con otros tableros.",
|
||||
"add-library-panel-button": "Añadir panel de biblioteca",
|
||||
@@ -874,6 +985,9 @@
|
||||
"import-a-dashboard-header": "Importar un tablero",
|
||||
"import-dashboard-button": "Importar panel de control"
|
||||
},
|
||||
"errors": {
|
||||
"failed-to-load": ""
|
||||
},
|
||||
"inspect": {
|
||||
"data-tab": "Datos",
|
||||
"error-tab": "Error",
|
||||
@@ -926,19 +1040,130 @@
|
||||
"rows": "Número total de filas",
|
||||
"table-title": "Estadísticas"
|
||||
},
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
},
|
||||
"panel-edit": {
|
||||
"alerting-tab": {
|
||||
"dashboard-not-saved": "",
|
||||
"no-rules": ""
|
||||
}
|
||||
},
|
||||
"responsive-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"hide-no-data": "",
|
||||
"title": ""
|
||||
},
|
||||
"name": "",
|
||||
"options": {
|
||||
"columns": "",
|
||||
"fixed": "",
|
||||
"min": "",
|
||||
"one-column": "",
|
||||
"rows": "",
|
||||
"three-columns": "",
|
||||
"two-columns": ""
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"row": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"new": "",
|
||||
"repeat": {
|
||||
"learn-more": "",
|
||||
"warning": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"height": {
|
||||
"expand": "",
|
||||
"hide-row-header": "",
|
||||
"min": "",
|
||||
"title": ""
|
||||
},
|
||||
"repeat": {
|
||||
"title": "",
|
||||
"variable": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"tabs-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"tab": {
|
||||
"new": ""
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Añadir",
|
||||
"add-panel": "",
|
||||
"add-panel-lib": "",
|
||||
"add-row": "",
|
||||
"add-tab": "",
|
||||
"alert-rules": "Reglas de alerta",
|
||||
"back-to-dashboard": "",
|
||||
"dashboard-settings": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"discard-library-panel-changes": "",
|
||||
"discard-panel": "",
|
||||
"discard-panel-new": "",
|
||||
"edit": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"edit-dashboard-v2-schema": "",
|
||||
"enter-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"exit-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"mark-favorite": "Marcar como favorito",
|
||||
"more-save-options": "",
|
||||
"open-original": "Abrir el panel de control original",
|
||||
"playlist-next": "Ir al siguiente panel de control",
|
||||
"playlist-previous": "Ir al panel de control anterior",
|
||||
"playlist-stop": "Detener la lista de reproducción",
|
||||
"public-dashboard": "",
|
||||
"refresh": "Actualizar panel de control",
|
||||
"save": "Guardar panel de control",
|
||||
"save-dashboard": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-copy": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-short": "",
|
||||
"save-library-panel": "",
|
||||
"settings": "Ajustes del panel de control",
|
||||
"share": "Compartir panel o panel de control",
|
||||
"share": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"share-button": "Compartir",
|
||||
"show-hidden-elements": "",
|
||||
"switch-old-dashboard": "",
|
||||
"unlink-library-panel": "",
|
||||
"unmark-favorite": "Deshacer marca como favorito"
|
||||
},
|
||||
"validation": {
|
||||
@@ -946,6 +1171,14 @@
|
||||
"invalid-json": "JSON no válido",
|
||||
"tags-expected-array": "etiquetas: matriz prevista",
|
||||
"tags-expected-strings": "etiquetas: matriz de cadenas prevista"
|
||||
},
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": "",
|
||||
"transparent-background": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard-import": {
|
||||
@@ -1048,6 +1281,12 @@
|
||||
"angular-deprecation-description": "",
|
||||
"angular-deprecation-heading": ""
|
||||
},
|
||||
"panel-queries": {
|
||||
"add-query-from-library": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-query-button": ""
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
@@ -1125,40 +1364,11 @@
|
||||
"scan-for-older-logs": "",
|
||||
"stop-scan": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-edit-description": "",
|
||||
"cancel": "",
|
||||
"default-description": "",
|
||||
"delete-query": "",
|
||||
"delete-query-text": "",
|
||||
"delete-query-title": "",
|
||||
"private": "",
|
||||
"public": "",
|
||||
"query-deleted": "",
|
||||
"query-template-add-error": "",
|
||||
"query-template-added": "",
|
||||
"query-template-edit-error": "",
|
||||
"query-template-edited": "",
|
||||
"save": ""
|
||||
},
|
||||
"query-template-modal": {
|
||||
"add-info": "",
|
||||
"add-title": "",
|
||||
"auto-star": "",
|
||||
"data-source-name": "",
|
||||
"description": "",
|
||||
"edit-info": "",
|
||||
"edit-title": "",
|
||||
"query": "",
|
||||
"visibility": ""
|
||||
},
|
||||
"query-template-modall": {
|
||||
"data-source-type": ""
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Cerrar el historial de consultas",
|
||||
"datasource-a-z": "Fuente de datos A-Z",
|
||||
"datasource-z-a": "Fuente de datos Z-A",
|
||||
"library-history-dropdown": "",
|
||||
"newest-first": "El más reciente primero",
|
||||
"oldest-first": "El más antiguo primero",
|
||||
"query-history": "Historial de consultas",
|
||||
@@ -1305,10 +1515,9 @@
|
||||
"sortBy": ""
|
||||
},
|
||||
"related-logs": {
|
||||
"docsLink": "",
|
||||
"LrrDocsLink": "",
|
||||
"openExploreLogs": "",
|
||||
"relatedLogsUnavailableAfterDocsLink": "",
|
||||
"relatedLogsUnavailableBeforeDocsLink": "",
|
||||
"relatedLogsUnavailable": "",
|
||||
"warnExperimentalFeature": ""
|
||||
},
|
||||
"viewBy": ""
|
||||
@@ -1357,6 +1566,24 @@
|
||||
"send-custom-feedback": ""
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "",
|
||||
"confirm-action": ""
|
||||
},
|
||||
"inline": {
|
||||
"add-action": "",
|
||||
"edit-action": ""
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "",
|
||||
"action-method": "",
|
||||
"action-query-params": "",
|
||||
"action-title": "",
|
||||
"action-title-placeholder": "",
|
||||
"one-click-description": ""
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "",
|
||||
"saving": ""
|
||||
@@ -1373,11 +1600,21 @@
|
||||
},
|
||||
"data-link-editor-modal": {
|
||||
"cancel": "",
|
||||
"one-click-description": "",
|
||||
"save": ""
|
||||
},
|
||||
"data-link-inline-editor": {
|
||||
"one-click": ""
|
||||
},
|
||||
"data-links-inline-editor": {
|
||||
"add-link": "",
|
||||
"one-click-link": ""
|
||||
"edit-link": "",
|
||||
"one-click": "",
|
||||
"one-click-enabled": "",
|
||||
"title-not-provided": "",
|
||||
"tooltip-edit": "",
|
||||
"tooltip-remove": "",
|
||||
"url-not-provided": ""
|
||||
},
|
||||
"data-source-http-settings": {
|
||||
"access-help": "",
|
||||
@@ -1458,7 +1695,12 @@
|
||||
"right-axis-indicator": ""
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"footer-add-annotation": ""
|
||||
"actions-confirmation-input-placeholder": "",
|
||||
"actions-confirmation-label": "",
|
||||
"actions-confirmation-message": "",
|
||||
"footer-add-annotation": "",
|
||||
"footer-click-to-action": "",
|
||||
"footer-click-to-navigate": ""
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
@@ -1780,7 +2022,8 @@
|
||||
},
|
||||
"log-row-message": {
|
||||
"ellipsis": "",
|
||||
"more": ""
|
||||
"more": "",
|
||||
"see-details": ""
|
||||
},
|
||||
"log-rows": {
|
||||
"disable-popover": {
|
||||
@@ -1792,6 +2035,13 @@
|
||||
"shortcut": ""
|
||||
}
|
||||
},
|
||||
"logs-navigation": {
|
||||
"newer-logs": "",
|
||||
"older-logs": "",
|
||||
"scroll-bottom": "",
|
||||
"scroll-top": "",
|
||||
"start-of-range": ""
|
||||
},
|
||||
"popover-menu": {
|
||||
"copy": "",
|
||||
"disable-menu": "",
|
||||
@@ -1881,6 +2131,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rule_groups": "",
|
||||
"alert_rules": "",
|
||||
"contact_points": "",
|
||||
"dashboards": "",
|
||||
@@ -1941,9 +2192,8 @@
|
||||
"public-preview": {
|
||||
"button-text": "",
|
||||
"message": "",
|
||||
"message-plugins": "",
|
||||
"title": "",
|
||||
"title-plugins": ""
|
||||
"message-cloud": "",
|
||||
"title": ""
|
||||
},
|
||||
"resource-details": {
|
||||
"dismiss-button": "",
|
||||
@@ -1986,6 +2236,7 @@
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "",
|
||||
"alert_rule_group": "",
|
||||
"contact_point": "",
|
||||
"dashboard": "Panel de control",
|
||||
"datasource": "Fuente de datos",
|
||||
@@ -2031,6 +2282,15 @@
|
||||
"title": "¿Por qué alojar con Grafana?"
|
||||
}
|
||||
},
|
||||
"multicombobox": {
|
||||
"all": {
|
||||
"title": "",
|
||||
"title-filtered": ""
|
||||
},
|
||||
"clear": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"add-new-connections": {
|
||||
"title": "Añadir nueva conexión"
|
||||
@@ -2384,7 +2644,8 @@
|
||||
"list-label": "Navegación",
|
||||
"open": "",
|
||||
"undock": "Desanclar el menú"
|
||||
}
|
||||
},
|
||||
"rss-button": ""
|
||||
},
|
||||
"news": {
|
||||
"drawer": {
|
||||
@@ -2516,19 +2777,35 @@
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"connections-tab": {
|
||||
"description": ""
|
||||
},
|
||||
"labels": {
|
||||
"contactGrafanaLabs": "",
|
||||
"customLinks": "",
|
||||
"customLinksTooltip": "",
|
||||
"dependencies": "",
|
||||
"documentation": "",
|
||||
"downloads": "",
|
||||
"from": "",
|
||||
"installedVersion": "",
|
||||
"lastCommitDate": "",
|
||||
"latestVersion": "",
|
||||
"links": "",
|
||||
"license": "",
|
||||
"raiseAnIssue": "",
|
||||
"reportAbuse": "",
|
||||
"reportAbuseTooltip": "",
|
||||
"repository": "",
|
||||
"signature": "",
|
||||
"status": "",
|
||||
"updatedAt": ""
|
||||
},
|
||||
"modal": {
|
||||
"cancel": "",
|
||||
"copyEmail": "",
|
||||
"description": "",
|
||||
"node": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"empty-state": {
|
||||
@@ -2743,14 +3020,6 @@
|
||||
"role-label": "Rol"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"datasource-names": "",
|
||||
"delete-query-button": "",
|
||||
"query-template-get-error": "",
|
||||
"search": "",
|
||||
"user-info-get-error": "",
|
||||
"user-names": ""
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Contraer la fila de la consulta",
|
||||
@@ -2760,7 +3029,6 @@
|
||||
"expand-row": "Expandir la fila de la consulta",
|
||||
"hide-response": "",
|
||||
"remove-query": "Eliminar consulta",
|
||||
"save-to-query-library": "",
|
||||
"show-response": "",
|
||||
"toggle-edit-mode": "Alternar el modo de edición de texto"
|
||||
},
|
||||
@@ -2862,6 +3130,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"save-dashboards": {
|
||||
"message-length": {
|
||||
"info": "",
|
||||
"title": ""
|
||||
},
|
||||
"name-exists": {
|
||||
"message-info": "",
|
||||
"message-suggestion": "",
|
||||
@@ -3145,6 +3417,7 @@
|
||||
"home-dashboard-placeholder": "Panel de control por defecto",
|
||||
"locale-label": "Idioma",
|
||||
"locale-placeholder": "Cambiar idioma",
|
||||
"theme-description": "",
|
||||
"theme-label": "Tema de interfaz de usuario",
|
||||
"week-start-label": "Inicio de la semana"
|
||||
},
|
||||
@@ -3216,6 +3489,13 @@
|
||||
"url-column-header": "URL de la instantánea",
|
||||
"view-button": "Vista"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"content": "",
|
||||
"show-all-series": "",
|
||||
"show-only-series": ""
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "",
|
||||
"loading": "Cargando...",
|
||||
@@ -3309,20 +3589,26 @@
|
||||
"start-your-metrics-exploration": "",
|
||||
"subtitle": ""
|
||||
},
|
||||
"metric-overview": {
|
||||
"description-label": "",
|
||||
"labels": "",
|
||||
"metric-attributes": "",
|
||||
"no-description": "",
|
||||
"type-label": "",
|
||||
"unit-label": "",
|
||||
"unknown-type": ""
|
||||
},
|
||||
"metric-select": {
|
||||
"filter-by": "",
|
||||
"native-histogram": "",
|
||||
"new-badge": "",
|
||||
"otel-switch": ""
|
||||
},
|
||||
"native-histogram-banner": {
|
||||
"ch-heatmap": "",
|
||||
"ch-histogram": "",
|
||||
"click-histogram": "",
|
||||
"hide-examples": "",
|
||||
"learn-more": "",
|
||||
"metric-examples": "",
|
||||
"nh-heatmap": "",
|
||||
"nh-histogram": "",
|
||||
"now": "",
|
||||
"previously": "",
|
||||
"see-examples": "",
|
||||
"sentence": ""
|
||||
},
|
||||
"recent-metrics": {
|
||||
"or-view-a-recent-exploration": ""
|
||||
},
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"save-button": ""
|
||||
}
|
||||
},
|
||||
"actions-editor": {
|
||||
"inline": {
|
||||
"add-button": "",
|
||||
"one-click-link": ""
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"anon-users": {
|
||||
"not-found": ""
|
||||
@@ -174,6 +168,24 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alert": {
|
||||
"alert-state": "",
|
||||
"annotations": "",
|
||||
"evaluation": "",
|
||||
"evaluation-paused": "",
|
||||
"evaluation-paused-description": "",
|
||||
"last-evaluated": "",
|
||||
"last-evaluation-duration": "",
|
||||
"last-updated-at": "",
|
||||
"last-updated-by": "",
|
||||
"no-annotations": "",
|
||||
"pending-period": "",
|
||||
"rule": "",
|
||||
"rule-identifier": "",
|
||||
"rule-type": "",
|
||||
"state-error-timeout": "",
|
||||
"state-no-data": ""
|
||||
},
|
||||
"alert-recording-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@@ -283,6 +295,7 @@
|
||||
"contactPointFilter": {
|
||||
"label": ""
|
||||
},
|
||||
"copy-to-clipboard": "",
|
||||
"export": {
|
||||
"subtitle": {
|
||||
"formats": "",
|
||||
@@ -299,6 +312,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"group-actions": {
|
||||
"actions-trigger": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"export": "",
|
||||
"reorder": ""
|
||||
},
|
||||
"list-view": {
|
||||
"empty": {
|
||||
"new-alert-rule": "",
|
||||
@@ -484,11 +504,23 @@
|
||||
},
|
||||
"rule-list": {
|
||||
"configure-datasource": "",
|
||||
"ds-error-boundary": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
},
|
||||
"filter-view": {
|
||||
"no-more-results": "",
|
||||
"no-rules-found": ""
|
||||
},
|
||||
"new-alert-rule": ""
|
||||
"new-alert-rule": "",
|
||||
"pagination": {
|
||||
"next-page": "",
|
||||
"previous-page": ""
|
||||
},
|
||||
"return-button": {
|
||||
"title": ""
|
||||
},
|
||||
"rulerrule-loading-error": ""
|
||||
},
|
||||
"rule-state": {
|
||||
"creating": "",
|
||||
@@ -690,7 +722,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"custom-value": {
|
||||
"label": ""
|
||||
"description": ""
|
||||
},
|
||||
"group": {
|
||||
"undefined": ""
|
||||
},
|
||||
"options": {
|
||||
"no-found": ""
|
||||
@@ -863,6 +898,82 @@
|
||||
"redirect-link": "Liste dans Alertes Grafana",
|
||||
"subtitle": "Règles d'alerte liées à ce tableau de bord"
|
||||
},
|
||||
"default-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"repeat": {
|
||||
"direction": {
|
||||
"horizontal": "",
|
||||
"title": "",
|
||||
"vertical": ""
|
||||
},
|
||||
"max": "",
|
||||
"title": "",
|
||||
"variable": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"row-actions": {
|
||||
"delete": "",
|
||||
"modal": {
|
||||
"alt-action": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"button": {
|
||||
"label": ""
|
||||
},
|
||||
"form": {
|
||||
"cancel": "",
|
||||
"repeat-for": {
|
||||
"label": "",
|
||||
"learn-more": "",
|
||||
"warning": {
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-pane": {
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"open": "",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "",
|
||||
"title": ""
|
||||
},
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"add-library-panel-body": "Ajoutez des visualisations partagées avec d'autres tableaux de bord.",
|
||||
"add-library-panel-button": "Ajouter un panneau Bibliothèque",
|
||||
@@ -874,6 +985,9 @@
|
||||
"import-a-dashboard-header": "Importer un tableau de bord",
|
||||
"import-dashboard-button": "Importer un tableau de bord"
|
||||
},
|
||||
"errors": {
|
||||
"failed-to-load": ""
|
||||
},
|
||||
"inspect": {
|
||||
"data-tab": "Données",
|
||||
"error-tab": "Erreur",
|
||||
@@ -926,19 +1040,130 @@
|
||||
"rows": "Nombre total de lignes",
|
||||
"table-title": "Statistiques"
|
||||
},
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
},
|
||||
"panel-edit": {
|
||||
"alerting-tab": {
|
||||
"dashboard-not-saved": "",
|
||||
"no-rules": ""
|
||||
}
|
||||
},
|
||||
"responsive-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"hide-no-data": "",
|
||||
"title": ""
|
||||
},
|
||||
"name": "",
|
||||
"options": {
|
||||
"columns": "",
|
||||
"fixed": "",
|
||||
"min": "",
|
||||
"one-column": "",
|
||||
"rows": "",
|
||||
"three-columns": "",
|
||||
"two-columns": ""
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"row": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"new": "",
|
||||
"repeat": {
|
||||
"learn-more": "",
|
||||
"warning": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"height": {
|
||||
"expand": "",
|
||||
"hide-row-header": "",
|
||||
"min": "",
|
||||
"title": ""
|
||||
},
|
||||
"repeat": {
|
||||
"title": "",
|
||||
"variable": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"tabs-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"tab": {
|
||||
"new": ""
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Ajouter",
|
||||
"add-panel": "",
|
||||
"add-panel-lib": "",
|
||||
"add-row": "",
|
||||
"add-tab": "",
|
||||
"alert-rules": "Règles d'alerte",
|
||||
"back-to-dashboard": "",
|
||||
"dashboard-settings": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"discard-library-panel-changes": "",
|
||||
"discard-panel": "",
|
||||
"discard-panel-new": "",
|
||||
"edit": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"edit-dashboard-v2-schema": "",
|
||||
"enter-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"exit-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"mark-favorite": "Marquer comme favori",
|
||||
"more-save-options": "",
|
||||
"open-original": "Ouvrir le tableau de bord d'origine",
|
||||
"playlist-next": "Accéder au tableau de bord suivant",
|
||||
"playlist-previous": "Accéder au tableau de bord précédent",
|
||||
"playlist-stop": "Arrêter la liste de lecture",
|
||||
"public-dashboard": "",
|
||||
"refresh": "Actualiser le tableau de bord",
|
||||
"save": "Enregistrer le tableau de bord",
|
||||
"save-dashboard": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-copy": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-short": "",
|
||||
"save-library-panel": "",
|
||||
"settings": "Paramètres du tableau de bord",
|
||||
"share": "Partager le tableau de bord ou le panneau",
|
||||
"share": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"share-button": "Partager",
|
||||
"show-hidden-elements": "",
|
||||
"switch-old-dashboard": "",
|
||||
"unlink-library-panel": "",
|
||||
"unmark-favorite": "Supprimer des favoris"
|
||||
},
|
||||
"validation": {
|
||||
@@ -946,6 +1171,14 @@
|
||||
"invalid-json": "JSON non valide",
|
||||
"tags-expected-array": "étiquettes attendues tableau",
|
||||
"tags-expected-strings": "étiquettes attendues tableau de chaînes"
|
||||
},
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": "",
|
||||
"transparent-background": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard-import": {
|
||||
@@ -1048,6 +1281,12 @@
|
||||
"angular-deprecation-description": "",
|
||||
"angular-deprecation-heading": ""
|
||||
},
|
||||
"panel-queries": {
|
||||
"add-query-from-library": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-query-button": ""
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
@@ -1125,40 +1364,11 @@
|
||||
"scan-for-older-logs": "",
|
||||
"stop-scan": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-edit-description": "",
|
||||
"cancel": "",
|
||||
"default-description": "",
|
||||
"delete-query": "",
|
||||
"delete-query-text": "",
|
||||
"delete-query-title": "",
|
||||
"private": "",
|
||||
"public": "",
|
||||
"query-deleted": "",
|
||||
"query-template-add-error": "",
|
||||
"query-template-added": "",
|
||||
"query-template-edit-error": "",
|
||||
"query-template-edited": "",
|
||||
"save": ""
|
||||
},
|
||||
"query-template-modal": {
|
||||
"add-info": "",
|
||||
"add-title": "",
|
||||
"auto-star": "",
|
||||
"data-source-name": "",
|
||||
"description": "",
|
||||
"edit-info": "",
|
||||
"edit-title": "",
|
||||
"query": "",
|
||||
"visibility": ""
|
||||
},
|
||||
"query-template-modall": {
|
||||
"data-source-type": ""
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Fermer l'historique des requêtes",
|
||||
"datasource-a-z": "Source de données A-Z",
|
||||
"datasource-z-a": "Source de données Z-A",
|
||||
"library-history-dropdown": "",
|
||||
"newest-first": "Plus récent en premier",
|
||||
"oldest-first": "Plus ancien en premier",
|
||||
"query-history": "Historique des requêtes",
|
||||
@@ -1305,10 +1515,9 @@
|
||||
"sortBy": ""
|
||||
},
|
||||
"related-logs": {
|
||||
"docsLink": "",
|
||||
"LrrDocsLink": "",
|
||||
"openExploreLogs": "",
|
||||
"relatedLogsUnavailableAfterDocsLink": "",
|
||||
"relatedLogsUnavailableBeforeDocsLink": "",
|
||||
"relatedLogsUnavailable": "",
|
||||
"warnExperimentalFeature": ""
|
||||
},
|
||||
"viewBy": ""
|
||||
@@ -1357,6 +1566,24 @@
|
||||
"send-custom-feedback": ""
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "",
|
||||
"confirm-action": ""
|
||||
},
|
||||
"inline": {
|
||||
"add-action": "",
|
||||
"edit-action": ""
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "",
|
||||
"action-method": "",
|
||||
"action-query-params": "",
|
||||
"action-title": "",
|
||||
"action-title-placeholder": "",
|
||||
"one-click-description": ""
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "",
|
||||
"saving": ""
|
||||
@@ -1373,11 +1600,21 @@
|
||||
},
|
||||
"data-link-editor-modal": {
|
||||
"cancel": "",
|
||||
"one-click-description": "",
|
||||
"save": ""
|
||||
},
|
||||
"data-link-inline-editor": {
|
||||
"one-click": ""
|
||||
},
|
||||
"data-links-inline-editor": {
|
||||
"add-link": "",
|
||||
"one-click-link": ""
|
||||
"edit-link": "",
|
||||
"one-click": "",
|
||||
"one-click-enabled": "",
|
||||
"title-not-provided": "",
|
||||
"tooltip-edit": "",
|
||||
"tooltip-remove": "",
|
||||
"url-not-provided": ""
|
||||
},
|
||||
"data-source-http-settings": {
|
||||
"access-help": "",
|
||||
@@ -1458,7 +1695,12 @@
|
||||
"right-axis-indicator": ""
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"footer-add-annotation": ""
|
||||
"actions-confirmation-input-placeholder": "",
|
||||
"actions-confirmation-label": "",
|
||||
"actions-confirmation-message": "",
|
||||
"footer-add-annotation": "",
|
||||
"footer-click-to-action": "",
|
||||
"footer-click-to-navigate": ""
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
@@ -1780,7 +2022,8 @@
|
||||
},
|
||||
"log-row-message": {
|
||||
"ellipsis": "",
|
||||
"more": ""
|
||||
"more": "",
|
||||
"see-details": ""
|
||||
},
|
||||
"log-rows": {
|
||||
"disable-popover": {
|
||||
@@ -1792,6 +2035,13 @@
|
||||
"shortcut": ""
|
||||
}
|
||||
},
|
||||
"logs-navigation": {
|
||||
"newer-logs": "",
|
||||
"older-logs": "",
|
||||
"scroll-bottom": "",
|
||||
"scroll-top": "",
|
||||
"start-of-range": ""
|
||||
},
|
||||
"popover-menu": {
|
||||
"copy": "",
|
||||
"disable-menu": "",
|
||||
@@ -1881,6 +2131,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rule_groups": "",
|
||||
"alert_rules": "",
|
||||
"contact_points": "",
|
||||
"dashboards": "",
|
||||
@@ -1941,9 +2192,8 @@
|
||||
"public-preview": {
|
||||
"button-text": "",
|
||||
"message": "",
|
||||
"message-plugins": "",
|
||||
"title": "",
|
||||
"title-plugins": ""
|
||||
"message-cloud": "",
|
||||
"title": ""
|
||||
},
|
||||
"resource-details": {
|
||||
"dismiss-button": "",
|
||||
@@ -1986,6 +2236,7 @@
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "",
|
||||
"alert_rule_group": "",
|
||||
"contact_point": "",
|
||||
"dashboard": "Tableau de bord",
|
||||
"datasource": "Source de données",
|
||||
@@ -2031,6 +2282,15 @@
|
||||
"title": "Pourquoi héberger avec Grafana ?"
|
||||
}
|
||||
},
|
||||
"multicombobox": {
|
||||
"all": {
|
||||
"title": "",
|
||||
"title-filtered": ""
|
||||
},
|
||||
"clear": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"add-new-connections": {
|
||||
"title": "Ajouter une nouvelle connexion"
|
||||
@@ -2384,7 +2644,8 @@
|
||||
"list-label": "Navigation",
|
||||
"open": "",
|
||||
"undock": "Ancrer le menu"
|
||||
}
|
||||
},
|
||||
"rss-button": ""
|
||||
},
|
||||
"news": {
|
||||
"drawer": {
|
||||
@@ -2516,19 +2777,35 @@
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"connections-tab": {
|
||||
"description": ""
|
||||
},
|
||||
"labels": {
|
||||
"contactGrafanaLabs": "",
|
||||
"customLinks": "",
|
||||
"customLinksTooltip": "",
|
||||
"dependencies": "",
|
||||
"documentation": "",
|
||||
"downloads": "",
|
||||
"from": "",
|
||||
"installedVersion": "",
|
||||
"lastCommitDate": "",
|
||||
"latestVersion": "",
|
||||
"links": "",
|
||||
"license": "",
|
||||
"raiseAnIssue": "",
|
||||
"reportAbuse": "",
|
||||
"reportAbuseTooltip": "",
|
||||
"repository": "",
|
||||
"signature": "",
|
||||
"status": "",
|
||||
"updatedAt": ""
|
||||
},
|
||||
"modal": {
|
||||
"cancel": "",
|
||||
"copyEmail": "",
|
||||
"description": "",
|
||||
"node": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"empty-state": {
|
||||
@@ -2743,14 +3020,6 @@
|
||||
"role-label": "Rôle"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"datasource-names": "",
|
||||
"delete-query-button": "",
|
||||
"query-template-get-error": "",
|
||||
"search": "",
|
||||
"user-info-get-error": "",
|
||||
"user-names": ""
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Réduire la ligne de requête",
|
||||
@@ -2760,7 +3029,6 @@
|
||||
"expand-row": "Développer la ligne de requête",
|
||||
"hide-response": "",
|
||||
"remove-query": "Supprimer la requête",
|
||||
"save-to-query-library": "",
|
||||
"show-response": "",
|
||||
"toggle-edit-mode": "Activer/désactiver le mode édition de texte"
|
||||
},
|
||||
@@ -2862,6 +3130,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"save-dashboards": {
|
||||
"message-length": {
|
||||
"info": "",
|
||||
"title": ""
|
||||
},
|
||||
"name-exists": {
|
||||
"message-info": "",
|
||||
"message-suggestion": "",
|
||||
@@ -3145,6 +3417,7 @@
|
||||
"home-dashboard-placeholder": "Tableau de bord par défaut",
|
||||
"locale-label": "Langue",
|
||||
"locale-placeholder": "Choisir une langue",
|
||||
"theme-description": "",
|
||||
"theme-label": "Thème de l'interface utilisateur",
|
||||
"week-start-label": "Début de la semaine"
|
||||
},
|
||||
@@ -3216,6 +3489,13 @@
|
||||
"url-column-header": "URL de l'instantané",
|
||||
"view-button": "Afficher"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"content": "",
|
||||
"show-all-series": "",
|
||||
"show-only-series": ""
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "",
|
||||
"loading": "Chargement en cours...",
|
||||
@@ -3309,20 +3589,26 @@
|
||||
"start-your-metrics-exploration": "",
|
||||
"subtitle": ""
|
||||
},
|
||||
"metric-overview": {
|
||||
"description-label": "",
|
||||
"labels": "",
|
||||
"metric-attributes": "",
|
||||
"no-description": "",
|
||||
"type-label": "",
|
||||
"unit-label": "",
|
||||
"unknown-type": ""
|
||||
},
|
||||
"metric-select": {
|
||||
"filter-by": "",
|
||||
"native-histogram": "",
|
||||
"new-badge": "",
|
||||
"otel-switch": ""
|
||||
},
|
||||
"native-histogram-banner": {
|
||||
"ch-heatmap": "",
|
||||
"ch-histogram": "",
|
||||
"click-histogram": "",
|
||||
"hide-examples": "",
|
||||
"learn-more": "",
|
||||
"metric-examples": "",
|
||||
"nh-heatmap": "",
|
||||
"nh-histogram": "",
|
||||
"now": "",
|
||||
"previously": "",
|
||||
"see-examples": "",
|
||||
"sentence": ""
|
||||
},
|
||||
"recent-metrics": {
|
||||
"or-view-a-recent-exploration": ""
|
||||
},
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"save-button": ""
|
||||
}
|
||||
},
|
||||
"actions-editor": {
|
||||
"inline": {
|
||||
"add-button": "",
|
||||
"one-click-link": ""
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"anon-users": {
|
||||
"not-found": ""
|
||||
@@ -174,6 +168,24 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alert": {
|
||||
"alert-state": "",
|
||||
"annotations": "",
|
||||
"evaluation": "",
|
||||
"evaluation-paused": "",
|
||||
"evaluation-paused-description": "",
|
||||
"last-evaluated": "",
|
||||
"last-evaluation-duration": "",
|
||||
"last-updated-at": "",
|
||||
"last-updated-by": "",
|
||||
"no-annotations": "",
|
||||
"pending-period": "",
|
||||
"rule": "",
|
||||
"rule-identifier": "",
|
||||
"rule-type": "",
|
||||
"state-error-timeout": "",
|
||||
"state-no-data": ""
|
||||
},
|
||||
"alert-recording-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@@ -283,6 +295,7 @@
|
||||
"contactPointFilter": {
|
||||
"label": ""
|
||||
},
|
||||
"copy-to-clipboard": "",
|
||||
"export": {
|
||||
"subtitle": {
|
||||
"formats": "",
|
||||
@@ -299,6 +312,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"group-actions": {
|
||||
"actions-trigger": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"export": "",
|
||||
"reorder": ""
|
||||
},
|
||||
"list-view": {
|
||||
"empty": {
|
||||
"new-alert-rule": "",
|
||||
@@ -484,11 +504,23 @@
|
||||
},
|
||||
"rule-list": {
|
||||
"configure-datasource": "",
|
||||
"ds-error-boundary": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
},
|
||||
"filter-view": {
|
||||
"no-more-results": "",
|
||||
"no-rules-found": ""
|
||||
},
|
||||
"new-alert-rule": ""
|
||||
"new-alert-rule": "",
|
||||
"pagination": {
|
||||
"next-page": "",
|
||||
"previous-page": ""
|
||||
},
|
||||
"return-button": {
|
||||
"title": ""
|
||||
},
|
||||
"rulerrule-loading-error": ""
|
||||
},
|
||||
"rule-state": {
|
||||
"creating": "",
|
||||
@@ -690,7 +722,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"custom-value": {
|
||||
"label": ""
|
||||
"description": ""
|
||||
},
|
||||
"group": {
|
||||
"undefined": ""
|
||||
},
|
||||
"options": {
|
||||
"no-found": ""
|
||||
@@ -863,6 +898,82 @@
|
||||
"redirect-link": "Lista no alerta do Grafana",
|
||||
"subtitle": "Regras de alerta relacionadas a este painel de controle"
|
||||
},
|
||||
"default-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"repeat": {
|
||||
"direction": {
|
||||
"horizontal": "",
|
||||
"title": "",
|
||||
"vertical": ""
|
||||
},
|
||||
"max": "",
|
||||
"title": "",
|
||||
"variable": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"row-actions": {
|
||||
"delete": "",
|
||||
"modal": {
|
||||
"alt-action": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"button": {
|
||||
"label": ""
|
||||
},
|
||||
"form": {
|
||||
"cancel": "",
|
||||
"repeat-for": {
|
||||
"label": "",
|
||||
"learn-more": "",
|
||||
"warning": {
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-pane": {
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"open": "",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "",
|
||||
"title": ""
|
||||
},
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"add-library-panel-body": "Adicione visualizações que são compartilhadas com outros painéis de controle.",
|
||||
"add-library-panel-button": "Adicionar painel de biblioteca",
|
||||
@@ -874,6 +985,9 @@
|
||||
"import-a-dashboard-header": "Importar um painel de controle",
|
||||
"import-dashboard-button": "Importar painel de controle"
|
||||
},
|
||||
"errors": {
|
||||
"failed-to-load": ""
|
||||
},
|
||||
"inspect": {
|
||||
"data-tab": "Dados",
|
||||
"error-tab": "Erro",
|
||||
@@ -926,19 +1040,130 @@
|
||||
"rows": "Número total de linhas",
|
||||
"table-title": "Estatísticas"
|
||||
},
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
},
|
||||
"panel-edit": {
|
||||
"alerting-tab": {
|
||||
"dashboard-not-saved": "",
|
||||
"no-rules": ""
|
||||
}
|
||||
},
|
||||
"responsive-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"hide-no-data": "",
|
||||
"title": ""
|
||||
},
|
||||
"name": "",
|
||||
"options": {
|
||||
"columns": "",
|
||||
"fixed": "",
|
||||
"min": "",
|
||||
"one-column": "",
|
||||
"rows": "",
|
||||
"three-columns": "",
|
||||
"two-columns": ""
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"row": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"new": "",
|
||||
"repeat": {
|
||||
"learn-more": "",
|
||||
"warning": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"height": {
|
||||
"expand": "",
|
||||
"hide-row-header": "",
|
||||
"min": "",
|
||||
"title": ""
|
||||
},
|
||||
"repeat": {
|
||||
"title": "",
|
||||
"variable": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"tabs-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"tab": {
|
||||
"new": ""
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "Adicionar",
|
||||
"add-panel": "",
|
||||
"add-panel-lib": "",
|
||||
"add-row": "",
|
||||
"add-tab": "",
|
||||
"alert-rules": "Regras de alerta",
|
||||
"back-to-dashboard": "",
|
||||
"dashboard-settings": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"discard-library-panel-changes": "",
|
||||
"discard-panel": "",
|
||||
"discard-panel-new": "",
|
||||
"edit": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"edit-dashboard-v2-schema": "",
|
||||
"enter-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"exit-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"mark-favorite": "Marcar como favorito",
|
||||
"more-save-options": "",
|
||||
"open-original": "Abrir painel de controle original",
|
||||
"playlist-next": "Ir para o próximo painel de controle",
|
||||
"playlist-previous": "Ir para o painel de controle anterior",
|
||||
"playlist-stop": "Parar lista de reprodução",
|
||||
"public-dashboard": "",
|
||||
"refresh": "Atualizar painel de controle",
|
||||
"save": "Salvar painel de controle",
|
||||
"save-dashboard": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-copy": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-short": "",
|
||||
"save-library-panel": "",
|
||||
"settings": "Configurações do painel de controle",
|
||||
"share": "Compartilhar painel de controle",
|
||||
"share": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"share-button": "Compartilhar",
|
||||
"show-hidden-elements": "",
|
||||
"switch-old-dashboard": "",
|
||||
"unlink-library-panel": "",
|
||||
"unmark-favorite": "Desmarcar como favorito"
|
||||
},
|
||||
"validation": {
|
||||
@@ -946,6 +1171,14 @@
|
||||
"invalid-json": "JSON inválido",
|
||||
"tags-expected-array": "matriz de tags esperada",
|
||||
"tags-expected-strings": "matriz de strings de tags esperada"
|
||||
},
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": "",
|
||||
"transparent-background": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard-import": {
|
||||
@@ -1048,6 +1281,12 @@
|
||||
"angular-deprecation-description": "",
|
||||
"angular-deprecation-heading": ""
|
||||
},
|
||||
"panel-queries": {
|
||||
"add-query-from-library": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-query-button": ""
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
@@ -1125,40 +1364,11 @@
|
||||
"scan-for-older-logs": "",
|
||||
"stop-scan": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-edit-description": "",
|
||||
"cancel": "",
|
||||
"default-description": "",
|
||||
"delete-query": "",
|
||||
"delete-query-text": "",
|
||||
"delete-query-title": "",
|
||||
"private": "",
|
||||
"public": "",
|
||||
"query-deleted": "",
|
||||
"query-template-add-error": "",
|
||||
"query-template-added": "",
|
||||
"query-template-edit-error": "",
|
||||
"query-template-edited": "",
|
||||
"save": ""
|
||||
},
|
||||
"query-template-modal": {
|
||||
"add-info": "",
|
||||
"add-title": "",
|
||||
"auto-star": "",
|
||||
"data-source-name": "",
|
||||
"description": "",
|
||||
"edit-info": "",
|
||||
"edit-title": "",
|
||||
"query": "",
|
||||
"visibility": ""
|
||||
},
|
||||
"query-template-modall": {
|
||||
"data-source-type": ""
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Fechar histórico de consultas",
|
||||
"datasource-a-z": "Fonte de dados A-Z",
|
||||
"datasource-z-a": "Fonte de dados Z-A",
|
||||
"library-history-dropdown": "",
|
||||
"newest-first": "Mais recentes primeiro",
|
||||
"oldest-first": "Mais antigos primeiro",
|
||||
"query-history": "Histórico de consultas",
|
||||
@@ -1305,10 +1515,9 @@
|
||||
"sortBy": ""
|
||||
},
|
||||
"related-logs": {
|
||||
"docsLink": "",
|
||||
"LrrDocsLink": "",
|
||||
"openExploreLogs": "",
|
||||
"relatedLogsUnavailableAfterDocsLink": "",
|
||||
"relatedLogsUnavailableBeforeDocsLink": "",
|
||||
"relatedLogsUnavailable": "",
|
||||
"warnExperimentalFeature": ""
|
||||
},
|
||||
"viewBy": ""
|
||||
@@ -1357,6 +1566,24 @@
|
||||
"send-custom-feedback": ""
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "",
|
||||
"confirm-action": ""
|
||||
},
|
||||
"inline": {
|
||||
"add-action": "",
|
||||
"edit-action": ""
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "",
|
||||
"action-method": "",
|
||||
"action-query-params": "",
|
||||
"action-title": "",
|
||||
"action-title-placeholder": "",
|
||||
"one-click-description": ""
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "",
|
||||
"saving": ""
|
||||
@@ -1373,11 +1600,21 @@
|
||||
},
|
||||
"data-link-editor-modal": {
|
||||
"cancel": "",
|
||||
"one-click-description": "",
|
||||
"save": ""
|
||||
},
|
||||
"data-link-inline-editor": {
|
||||
"one-click": ""
|
||||
},
|
||||
"data-links-inline-editor": {
|
||||
"add-link": "",
|
||||
"one-click-link": ""
|
||||
"edit-link": "",
|
||||
"one-click": "",
|
||||
"one-click-enabled": "",
|
||||
"title-not-provided": "",
|
||||
"tooltip-edit": "",
|
||||
"tooltip-remove": "",
|
||||
"url-not-provided": ""
|
||||
},
|
||||
"data-source-http-settings": {
|
||||
"access-help": "",
|
||||
@@ -1458,7 +1695,12 @@
|
||||
"right-axis-indicator": ""
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"footer-add-annotation": ""
|
||||
"actions-confirmation-input-placeholder": "",
|
||||
"actions-confirmation-label": "",
|
||||
"actions-confirmation-message": "",
|
||||
"footer-add-annotation": "",
|
||||
"footer-click-to-action": "",
|
||||
"footer-click-to-navigate": ""
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
@@ -1780,7 +2022,8 @@
|
||||
},
|
||||
"log-row-message": {
|
||||
"ellipsis": "",
|
||||
"more": ""
|
||||
"more": "",
|
||||
"see-details": ""
|
||||
},
|
||||
"log-rows": {
|
||||
"disable-popover": {
|
||||
@@ -1792,6 +2035,13 @@
|
||||
"shortcut": ""
|
||||
}
|
||||
},
|
||||
"logs-navigation": {
|
||||
"newer-logs": "",
|
||||
"older-logs": "",
|
||||
"scroll-bottom": "",
|
||||
"scroll-top": "",
|
||||
"start-of-range": ""
|
||||
},
|
||||
"popover-menu": {
|
||||
"copy": "",
|
||||
"disable-menu": "",
|
||||
@@ -1881,6 +2131,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rule_groups": "",
|
||||
"alert_rules": "",
|
||||
"contact_points": "",
|
||||
"dashboards": "",
|
||||
@@ -1941,9 +2192,8 @@
|
||||
"public-preview": {
|
||||
"button-text": "",
|
||||
"message": "",
|
||||
"message-plugins": "",
|
||||
"title": "",
|
||||
"title-plugins": ""
|
||||
"message-cloud": "",
|
||||
"title": ""
|
||||
},
|
||||
"resource-details": {
|
||||
"dismiss-button": "",
|
||||
@@ -1986,6 +2236,7 @@
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "",
|
||||
"alert_rule_group": "",
|
||||
"contact_point": "",
|
||||
"dashboard": "Painel de controle",
|
||||
"datasource": "Fonte de dados",
|
||||
@@ -2031,6 +2282,15 @@
|
||||
"title": "Por que hospedar com o Grafana?"
|
||||
}
|
||||
},
|
||||
"multicombobox": {
|
||||
"all": {
|
||||
"title": "",
|
||||
"title-filtered": ""
|
||||
},
|
||||
"clear": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"add-new-connections": {
|
||||
"title": "Adicionar nova conexão"
|
||||
@@ -2384,7 +2644,8 @@
|
||||
"list-label": "Navegação",
|
||||
"open": "",
|
||||
"undock": "Desacoplar menu"
|
||||
}
|
||||
},
|
||||
"rss-button": ""
|
||||
},
|
||||
"news": {
|
||||
"drawer": {
|
||||
@@ -2516,19 +2777,35 @@
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"connections-tab": {
|
||||
"description": ""
|
||||
},
|
||||
"labels": {
|
||||
"contactGrafanaLabs": "",
|
||||
"customLinks": "",
|
||||
"customLinksTooltip": "",
|
||||
"dependencies": "",
|
||||
"documentation": "",
|
||||
"downloads": "",
|
||||
"from": "",
|
||||
"installedVersion": "",
|
||||
"lastCommitDate": "",
|
||||
"latestVersion": "",
|
||||
"links": "",
|
||||
"license": "",
|
||||
"raiseAnIssue": "",
|
||||
"reportAbuse": "",
|
||||
"reportAbuseTooltip": "",
|
||||
"repository": "",
|
||||
"signature": "",
|
||||
"status": "",
|
||||
"updatedAt": ""
|
||||
},
|
||||
"modal": {
|
||||
"cancel": "",
|
||||
"copyEmail": "",
|
||||
"description": "",
|
||||
"node": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"empty-state": {
|
||||
@@ -2743,14 +3020,6 @@
|
||||
"role-label": "Função"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"datasource-names": "",
|
||||
"delete-query-button": "",
|
||||
"query-template-get-error": "",
|
||||
"search": "",
|
||||
"user-info-get-error": "",
|
||||
"user-names": ""
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Recolher linha de consulta",
|
||||
@@ -2760,7 +3029,6 @@
|
||||
"expand-row": "Expandir linha de consulta",
|
||||
"hide-response": "",
|
||||
"remove-query": "Remover consulta",
|
||||
"save-to-query-library": "",
|
||||
"show-response": "",
|
||||
"toggle-edit-mode": "Alternar modo de edição de texto"
|
||||
},
|
||||
@@ -2862,6 +3130,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"save-dashboards": {
|
||||
"message-length": {
|
||||
"info": "",
|
||||
"title": ""
|
||||
},
|
||||
"name-exists": {
|
||||
"message-info": "",
|
||||
"message-suggestion": "",
|
||||
@@ -3145,6 +3417,7 @@
|
||||
"home-dashboard-placeholder": "Painel de controle padrão",
|
||||
"locale-label": "Idioma",
|
||||
"locale-placeholder": "Escolher idioma",
|
||||
"theme-description": "",
|
||||
"theme-label": "Tema da interface",
|
||||
"week-start-label": "Início da semana"
|
||||
},
|
||||
@@ -3216,6 +3489,13 @@
|
||||
"url-column-header": "URL da captura",
|
||||
"view-button": "Visualizar"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"content": "",
|
||||
"show-all-series": "",
|
||||
"show-only-series": ""
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "",
|
||||
"loading": "Carregando...",
|
||||
@@ -3309,20 +3589,26 @@
|
||||
"start-your-metrics-exploration": "",
|
||||
"subtitle": ""
|
||||
},
|
||||
"metric-overview": {
|
||||
"description-label": "",
|
||||
"labels": "",
|
||||
"metric-attributes": "",
|
||||
"no-description": "",
|
||||
"type-label": "",
|
||||
"unit-label": "",
|
||||
"unknown-type": ""
|
||||
},
|
||||
"metric-select": {
|
||||
"filter-by": "",
|
||||
"native-histogram": "",
|
||||
"new-badge": "",
|
||||
"otel-switch": ""
|
||||
},
|
||||
"native-histogram-banner": {
|
||||
"ch-heatmap": "",
|
||||
"ch-histogram": "",
|
||||
"click-histogram": "",
|
||||
"hide-examples": "",
|
||||
"learn-more": "",
|
||||
"metric-examples": "",
|
||||
"nh-heatmap": "",
|
||||
"nh-histogram": "",
|
||||
"now": "",
|
||||
"previously": "",
|
||||
"see-examples": "",
|
||||
"sentence": ""
|
||||
},
|
||||
"recent-metrics": {
|
||||
"or-view-a-recent-exploration": ""
|
||||
},
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"save-button": ""
|
||||
}
|
||||
},
|
||||
"actions-editor": {
|
||||
"inline": {
|
||||
"add-button": "",
|
||||
"one-click-link": ""
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"anon-users": {
|
||||
"not-found": ""
|
||||
@@ -174,6 +168,24 @@
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alert": {
|
||||
"alert-state": "",
|
||||
"annotations": "",
|
||||
"evaluation": "",
|
||||
"evaluation-paused": "",
|
||||
"evaluation-paused-description": "",
|
||||
"last-evaluated": "",
|
||||
"last-evaluation-duration": "",
|
||||
"last-updated-at": "",
|
||||
"last-updated-by": "",
|
||||
"no-annotations": "",
|
||||
"pending-period": "",
|
||||
"rule": "",
|
||||
"rule-identifier": "",
|
||||
"rule-type": "",
|
||||
"state-error-timeout": "",
|
||||
"state-no-data": ""
|
||||
},
|
||||
"alert-recording-rule-form": {
|
||||
"evaluation-behaviour": {
|
||||
"description": {
|
||||
@@ -281,6 +293,7 @@
|
||||
"contactPointFilter": {
|
||||
"label": ""
|
||||
},
|
||||
"copy-to-clipboard": "",
|
||||
"export": {
|
||||
"subtitle": {
|
||||
"formats": "",
|
||||
@@ -297,6 +310,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"group-actions": {
|
||||
"actions-trigger": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"export": "",
|
||||
"reorder": ""
|
||||
},
|
||||
"list-view": {
|
||||
"empty": {
|
||||
"new-alert-rule": "",
|
||||
@@ -480,11 +500,23 @@
|
||||
},
|
||||
"rule-list": {
|
||||
"configure-datasource": "",
|
||||
"ds-error-boundary": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
},
|
||||
"filter-view": {
|
||||
"no-more-results": "",
|
||||
"no-rules-found": ""
|
||||
},
|
||||
"new-alert-rule": ""
|
||||
"new-alert-rule": "",
|
||||
"pagination": {
|
||||
"next-page": "",
|
||||
"previous-page": ""
|
||||
},
|
||||
"return-button": {
|
||||
"title": ""
|
||||
},
|
||||
"rulerrule-loading-error": ""
|
||||
},
|
||||
"rule-state": {
|
||||
"creating": "",
|
||||
@@ -681,7 +713,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"custom-value": {
|
||||
"label": ""
|
||||
"description": ""
|
||||
},
|
||||
"group": {
|
||||
"undefined": ""
|
||||
},
|
||||
"options": {
|
||||
"no-found": ""
|
||||
@@ -854,6 +889,82 @@
|
||||
"redirect-link": "Grafana Alerting 中的列表",
|
||||
"subtitle": "与此仪表板相关的警报规则"
|
||||
},
|
||||
"default-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"repeat": {
|
||||
"direction": {
|
||||
"horizontal": "",
|
||||
"title": "",
|
||||
"vertical": ""
|
||||
},
|
||||
"max": "",
|
||||
"title": "",
|
||||
"variable": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"row-actions": {
|
||||
"delete": "",
|
||||
"modal": {
|
||||
"alt-action": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"button": {
|
||||
"label": ""
|
||||
},
|
||||
"form": {
|
||||
"cancel": "",
|
||||
"repeat-for": {
|
||||
"label": "",
|
||||
"learn-more": "",
|
||||
"warning": {
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"modal": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-pane": {
|
||||
"objects": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"open": "",
|
||||
"panels": {
|
||||
"multi-select": {
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"row": {
|
||||
"header": {
|
||||
"hide": "",
|
||||
"title": ""
|
||||
},
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
},
|
||||
"tab": {
|
||||
"multi-select": {
|
||||
"options-header": "",
|
||||
"selection-number": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"add-library-panel-body": "添加与其他仪表板共享的可视化。",
|
||||
"add-library-panel-button": "添加库面板",
|
||||
@@ -865,6 +976,9 @@
|
||||
"import-a-dashboard-header": "导入仪表板",
|
||||
"import-dashboard-button": "导入仪表板"
|
||||
},
|
||||
"errors": {
|
||||
"failed-to-load": ""
|
||||
},
|
||||
"inspect": {
|
||||
"data-tab": "数据",
|
||||
"error-tab": "错误",
|
||||
@@ -917,19 +1031,130 @@
|
||||
"rows": "总行数",
|
||||
"table-title": "统计信息"
|
||||
},
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
},
|
||||
"panel-edit": {
|
||||
"alerting-tab": {
|
||||
"dashboard-not-saved": "",
|
||||
"no-rules": ""
|
||||
}
|
||||
},
|
||||
"responsive-layout": {
|
||||
"description": "",
|
||||
"item-options": {
|
||||
"hide-no-data": "",
|
||||
"title": ""
|
||||
},
|
||||
"name": "",
|
||||
"options": {
|
||||
"columns": "",
|
||||
"fixed": "",
|
||||
"min": "",
|
||||
"one-column": "",
|
||||
"rows": "",
|
||||
"three-columns": "",
|
||||
"two-columns": ""
|
||||
}
|
||||
},
|
||||
"rows-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"row": {
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"new": "",
|
||||
"repeat": {
|
||||
"learn-more": "",
|
||||
"warning": ""
|
||||
}
|
||||
},
|
||||
"row-options": {
|
||||
"height": {
|
||||
"expand": "",
|
||||
"hide-row-header": "",
|
||||
"min": "",
|
||||
"title": ""
|
||||
},
|
||||
"repeat": {
|
||||
"title": "",
|
||||
"variable": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"tabs-layout": {
|
||||
"description": "",
|
||||
"name": "",
|
||||
"tab": {
|
||||
"new": ""
|
||||
},
|
||||
"tab-options": {
|
||||
"title": "",
|
||||
"title-option": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"add": "添加",
|
||||
"add-panel": "",
|
||||
"add-panel-lib": "",
|
||||
"add-row": "",
|
||||
"add-tab": "",
|
||||
"alert-rules": "警报规则",
|
||||
"back-to-dashboard": "",
|
||||
"dashboard-settings": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"discard-library-panel-changes": "",
|
||||
"discard-panel": "",
|
||||
"discard-panel-new": "",
|
||||
"edit": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"edit-dashboard-v2-schema": "",
|
||||
"enter-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"exit-edit-mode": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"mark-favorite": "标记为收藏",
|
||||
"more-save-options": "",
|
||||
"open-original": "打开原始仪表板",
|
||||
"playlist-next": "前往下一个仪表板",
|
||||
"playlist-previous": "前往上一个仪表板",
|
||||
"playlist-stop": "停止播放列表",
|
||||
"public-dashboard": "",
|
||||
"refresh": "刷新仪表板",
|
||||
"save": "保存仪表板",
|
||||
"save-dashboard": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-copy": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"save-dashboard-short": "",
|
||||
"save-library-panel": "",
|
||||
"settings": "仪表板设置",
|
||||
"share": "分享仪表板或面板",
|
||||
"share": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
},
|
||||
"share-button": "分享",
|
||||
"show-hidden-elements": "",
|
||||
"switch-old-dashboard": "",
|
||||
"unlink-library-panel": "",
|
||||
"unmark-favorite": "取消标记为收藏"
|
||||
},
|
||||
"validation": {
|
||||
@@ -937,6 +1162,14 @@
|
||||
"invalid-json": "无有效的 JSON",
|
||||
"tags-expected-array": "标签预期数组",
|
||||
"tags-expected-strings": "标签预期字符串数组"
|
||||
},
|
||||
"viz-panel": {
|
||||
"options": {
|
||||
"description": "",
|
||||
"title": "",
|
||||
"title-option": "",
|
||||
"transparent-background": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard-import": {
|
||||
@@ -1039,6 +1272,12 @@
|
||||
"angular-deprecation-description": "",
|
||||
"angular-deprecation-heading": ""
|
||||
},
|
||||
"panel-queries": {
|
||||
"add-query-from-library": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-query-button": ""
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
@@ -1116,40 +1355,11 @@
|
||||
"scan-for-older-logs": "",
|
||||
"stop-scan": ""
|
||||
},
|
||||
"query-library": {
|
||||
"add-edit-description": "",
|
||||
"cancel": "",
|
||||
"default-description": "",
|
||||
"delete-query": "",
|
||||
"delete-query-text": "",
|
||||
"delete-query-title": "",
|
||||
"private": "",
|
||||
"public": "",
|
||||
"query-deleted": "",
|
||||
"query-template-add-error": "",
|
||||
"query-template-added": "",
|
||||
"query-template-edit-error": "",
|
||||
"query-template-edited": "",
|
||||
"save": ""
|
||||
},
|
||||
"query-template-modal": {
|
||||
"add-info": "",
|
||||
"add-title": "",
|
||||
"auto-star": "",
|
||||
"data-source-name": "",
|
||||
"description": "",
|
||||
"edit-info": "",
|
||||
"edit-title": "",
|
||||
"query": "",
|
||||
"visibility": ""
|
||||
},
|
||||
"query-template-modall": {
|
||||
"data-source-type": ""
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "关闭查询历史记录",
|
||||
"datasource-a-z": "数据源 A-Z",
|
||||
"datasource-z-a": "数据源 Z-A",
|
||||
"library-history-dropdown": "",
|
||||
"newest-first": "最新在前",
|
||||
"oldest-first": "最早在前",
|
||||
"query-history": "查询历史记录",
|
||||
@@ -1296,10 +1506,9 @@
|
||||
"sortBy": ""
|
||||
},
|
||||
"related-logs": {
|
||||
"docsLink": "",
|
||||
"LrrDocsLink": "",
|
||||
"openExploreLogs": "",
|
||||
"relatedLogsUnavailableAfterDocsLink": "",
|
||||
"relatedLogsUnavailableBeforeDocsLink": "",
|
||||
"relatedLogsUnavailable": "",
|
||||
"warnExperimentalFeature": ""
|
||||
},
|
||||
"viewBy": ""
|
||||
@@ -1348,6 +1557,24 @@
|
||||
"send-custom-feedback": ""
|
||||
},
|
||||
"grafana-ui": {
|
||||
"action-editor": {
|
||||
"button": {
|
||||
"confirm": "",
|
||||
"confirm-action": ""
|
||||
},
|
||||
"inline": {
|
||||
"add-action": "",
|
||||
"edit-action": ""
|
||||
},
|
||||
"modal": {
|
||||
"action-body": "",
|
||||
"action-method": "",
|
||||
"action-query-params": "",
|
||||
"action-title": "",
|
||||
"action-title-placeholder": "",
|
||||
"one-click-description": ""
|
||||
}
|
||||
},
|
||||
"auto-save-field": {
|
||||
"saved": "",
|
||||
"saving": ""
|
||||
@@ -1364,11 +1591,21 @@
|
||||
},
|
||||
"data-link-editor-modal": {
|
||||
"cancel": "",
|
||||
"one-click-description": "",
|
||||
"save": ""
|
||||
},
|
||||
"data-link-inline-editor": {
|
||||
"one-click": ""
|
||||
},
|
||||
"data-links-inline-editor": {
|
||||
"add-link": "",
|
||||
"one-click-link": ""
|
||||
"edit-link": "",
|
||||
"one-click": "",
|
||||
"one-click-enabled": "",
|
||||
"title-not-provided": "",
|
||||
"tooltip-edit": "",
|
||||
"tooltip-remove": "",
|
||||
"url-not-provided": ""
|
||||
},
|
||||
"data-source-http-settings": {
|
||||
"access-help": "",
|
||||
@@ -1449,7 +1686,12 @@
|
||||
"right-axis-indicator": ""
|
||||
},
|
||||
"viz-tooltip": {
|
||||
"footer-add-annotation": ""
|
||||
"actions-confirmation-input-placeholder": "",
|
||||
"actions-confirmation-label": "",
|
||||
"actions-confirmation-message": "",
|
||||
"footer-add-annotation": "",
|
||||
"footer-click-to-action": "",
|
||||
"footer-click-to-navigate": ""
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
@@ -1770,7 +2012,8 @@
|
||||
},
|
||||
"log-row-message": {
|
||||
"ellipsis": "",
|
||||
"more": ""
|
||||
"more": "",
|
||||
"see-details": ""
|
||||
},
|
||||
"log-rows": {
|
||||
"disable-popover": {
|
||||
@@ -1782,6 +2025,13 @@
|
||||
"shortcut": ""
|
||||
}
|
||||
},
|
||||
"logs-navigation": {
|
||||
"newer-logs": "",
|
||||
"older-logs": "",
|
||||
"scroll-bottom": "",
|
||||
"scroll-top": "",
|
||||
"start-of-range": ""
|
||||
},
|
||||
"popover-menu": {
|
||||
"copy": "",
|
||||
"disable-menu": "",
|
||||
@@ -1871,6 +2121,7 @@
|
||||
"title": ""
|
||||
},
|
||||
"migrated-counts": {
|
||||
"alert_rule_groups": "",
|
||||
"alert_rules": "",
|
||||
"contact_points": "",
|
||||
"dashboards": "",
|
||||
@@ -1931,9 +2182,8 @@
|
||||
"public-preview": {
|
||||
"button-text": "",
|
||||
"message": "",
|
||||
"message-plugins": "",
|
||||
"title": "",
|
||||
"title-plugins": ""
|
||||
"message-cloud": "",
|
||||
"title": ""
|
||||
},
|
||||
"resource-details": {
|
||||
"dismiss-button": "",
|
||||
@@ -1976,6 +2226,7 @@
|
||||
},
|
||||
"resource-type": {
|
||||
"alert_rule": "",
|
||||
"alert_rule_group": "",
|
||||
"contact_point": "",
|
||||
"dashboard": "仪表板",
|
||||
"datasource": "数据源",
|
||||
@@ -2021,6 +2272,15 @@
|
||||
"title": "为什么使用 Grafana 托管?"
|
||||
}
|
||||
},
|
||||
"multicombobox": {
|
||||
"all": {
|
||||
"title": "",
|
||||
"title-filtered": ""
|
||||
},
|
||||
"clear": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"nav": {
|
||||
"add-new-connections": {
|
||||
"title": "添加新连接"
|
||||
@@ -2374,7 +2634,8 @@
|
||||
"list-label": "导航",
|
||||
"open": "",
|
||||
"undock": "取消停靠菜单"
|
||||
}
|
||||
},
|
||||
"rss-button": ""
|
||||
},
|
||||
"news": {
|
||||
"drawer": {
|
||||
@@ -2506,19 +2767,35 @@
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"connections-tab": {
|
||||
"description": ""
|
||||
},
|
||||
"labels": {
|
||||
"contactGrafanaLabs": "",
|
||||
"customLinks": "",
|
||||
"customLinksTooltip": "",
|
||||
"dependencies": "",
|
||||
"documentation": "",
|
||||
"downloads": "",
|
||||
"from": "",
|
||||
"installedVersion": "",
|
||||
"lastCommitDate": "",
|
||||
"latestVersion": "",
|
||||
"links": "",
|
||||
"license": "",
|
||||
"raiseAnIssue": "",
|
||||
"reportAbuse": "",
|
||||
"reportAbuseTooltip": "",
|
||||
"repository": "",
|
||||
"signature": "",
|
||||
"status": "",
|
||||
"updatedAt": ""
|
||||
},
|
||||
"modal": {
|
||||
"cancel": "",
|
||||
"copyEmail": "",
|
||||
"description": "",
|
||||
"node": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"empty-state": {
|
||||
@@ -2732,14 +3009,6 @@
|
||||
"role-label": "角色"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"datasource-names": "",
|
||||
"delete-query-button": "",
|
||||
"query-template-get-error": "",
|
||||
"search": "",
|
||||
"user-info-get-error": "",
|
||||
"user-names": ""
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "折叠查询行",
|
||||
@@ -2749,7 +3018,6 @@
|
||||
"expand-row": "展开查询行",
|
||||
"hide-response": "",
|
||||
"remove-query": "删除查询",
|
||||
"save-to-query-library": "",
|
||||
"show-response": "",
|
||||
"toggle-edit-mode": "切换文本编辑模式"
|
||||
},
|
||||
@@ -2848,6 +3116,10 @@
|
||||
"title": ""
|
||||
},
|
||||
"save-dashboards": {
|
||||
"message-length": {
|
||||
"info": "",
|
||||
"title": ""
|
||||
},
|
||||
"name-exists": {
|
||||
"message-info": "",
|
||||
"message-suggestion": "",
|
||||
@@ -3131,6 +3403,7 @@
|
||||
"home-dashboard-placeholder": "默认仪表板",
|
||||
"locale-label": "语言",
|
||||
"locale-placeholder": "选择语言",
|
||||
"theme-description": "",
|
||||
"theme-label": "UI 主题",
|
||||
"week-start-label": "每周开始日"
|
||||
},
|
||||
@@ -3202,6 +3475,13 @@
|
||||
"url-column-header": "快照网址",
|
||||
"view-button": "查看"
|
||||
},
|
||||
"table": {
|
||||
"container": {
|
||||
"content": "",
|
||||
"show-all-series": "",
|
||||
"show-only-series": ""
|
||||
}
|
||||
},
|
||||
"tag-filter": {
|
||||
"clear-button": "",
|
||||
"loading": "加载中...",
|
||||
@@ -3295,20 +3575,26 @@
|
||||
"start-your-metrics-exploration": "",
|
||||
"subtitle": ""
|
||||
},
|
||||
"metric-overview": {
|
||||
"description-label": "",
|
||||
"labels": "",
|
||||
"metric-attributes": "",
|
||||
"no-description": "",
|
||||
"type-label": "",
|
||||
"unit-label": "",
|
||||
"unknown-type": ""
|
||||
},
|
||||
"metric-select": {
|
||||
"filter-by": "",
|
||||
"native-histogram": "",
|
||||
"new-badge": "",
|
||||
"otel-switch": ""
|
||||
},
|
||||
"native-histogram-banner": {
|
||||
"ch-heatmap": "",
|
||||
"ch-histogram": "",
|
||||
"click-histogram": "",
|
||||
"hide-examples": "",
|
||||
"learn-more": "",
|
||||
"metric-examples": "",
|
||||
"nh-heatmap": "",
|
||||
"nh-histogram": "",
|
||||
"now": "",
|
||||
"previously": "",
|
||||
"see-examples": "",
|
||||
"sentence": ""
|
||||
},
|
||||
"recent-metrics": {
|
||||
"or-view-a-recent-exploration": ""
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user