diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 3b44630965e..c1a38731291 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -143,6 +143,11 @@ module.exports = [ message: 'Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.', }, + { + selector: 'CallExpression[callee.type="MemberExpression"][callee.property.name="localeCompare"]', + message: + 'Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.', + }, ], }, }, diff --git a/.betterer.results b/.betterer.results index ca7e75b3558..6429e7b67b5 100644 --- a/.betterer.results +++ b/.betterer.results @@ -149,6 +149,9 @@ exports[`better eslint`] = { "packages/grafana-data/src/themes/createColors.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "packages/grafana-data/src/themes/registry.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -450,7 +453,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], "packages/grafana-prometheus/src/language_utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -597,6 +601,9 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/Combobox/useOptions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "packages/grafana-ui/src/components/ConfirmModal/ConfirmContent.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -625,6 +632,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], + "packages/grafana-ui/src/components/Icon/Icon.story.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], + "packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], "packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -756,7 +770,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "6"] ], "packages/grafana-ui/src/components/Tags/Tag.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -913,6 +928,15 @@ exports[`better eslint`] = { "public/app/core/components/Page/EditableTitle.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], + "public/app/core/components/RolePicker/RolePickerMenu.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "2"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "3"] + ], "public/app/core/components/RolePickerDrawer/RolePickerDrawer.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -922,7 +946,9 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "6"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "7"] ], "public/app/core/components/Signup/SignupPage.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -965,6 +991,10 @@ exports[`better eslint`] = { [0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "1"], [0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "2"] ], + "public/app/core/reducers/navBarTree.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], "public/app/core/services/ResponseQueue.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1066,12 +1096,21 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"] ], + "public/app/features/actions/ActionVariablesEditor.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/actions/ParamsEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] ], "public/app/features/admin/AdminEditOrgPage.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/admin/AdminFeatureTogglesTable.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "2"] + ], "public/app/features/admin/ServerStatsCard.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -1187,6 +1226,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/contact-points/components/ContactPointsFilter.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/alerting/unified/components/contact-points/utils.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/components/create-folder/CreateNewFolder.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -1284,6 +1326,9 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -1299,7 +1344,8 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "7"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "8"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "8"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "9"] ], "public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -1364,6 +1410,13 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"] ], + "public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], + "public/app/features/alerting/unified/components/rules/RuleListStateView.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], @@ -1403,6 +1456,9 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/hooks/useAlertmanagerConfig.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/hooks/useControlledFieldArray.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1426,9 +1482,16 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/features/alerting/unified/rule-list/StateView.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/types/receiver-form.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], + "public/app/features/alerting/unified/utils/datasource.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], "public/app/features/alerting/unified/utils/misc.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -1450,6 +1513,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], + "public/app/features/alerting/unified/utils/rule-id.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/alerting/unified/utils/rules.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -1517,6 +1583,9 @@ exports[`better eslint`] = { "public/app/features/connections/tabs/ConnectData/index.tsx:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"] ], + "public/app/features/correlations/CorrelationsPage.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/correlations/Forms/ConfigureCorrelationBasicInfoForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] @@ -1689,7 +1758,8 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "6"] ], "public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -1818,7 +1888,8 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "7"] ], "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -2199,7 +2270,9 @@ exports[`better eslint`] = { ], "public/app/features/datasources/state/selectors.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "2"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "3"] ], "public/app/features/dimensions/editors/FileUploader.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] @@ -2265,6 +2338,9 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"] ], + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./TimelineHeaderRow\`)", "0"] ], @@ -2297,6 +2373,9 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DraggableManagerDemo\`)", "0"] ], + "public/app/features/explore/TraceView/components/utils/sort.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/features/explore/TraceView/createSpanLink.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -2499,6 +2578,10 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/plugins/admin/helpers.ts:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], "public/app/features/plugins/admin/pages/Browse.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -2557,20 +2640,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] ], - "public/app/features/provisioning/Config/ConfigForm.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "7"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "8"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "9"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "10"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "11"] - ], "public/app/features/provisioning/Config/ConfigFormGithubCollapse.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] @@ -2585,27 +2654,6 @@ exports[`better eslint`] = { "public/app/features/provisioning/GettingStarted/SidebarItem.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], - "public/app/features/provisioning/Job/RecentJobs.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/features/provisioning/Repository/RepositoryCard.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/features/provisioning/Repository/RepositoryOverview.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], - [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"] - ], - "public/app/features/provisioning/Wizard/FinishStep.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"] - ], - "public/app/features/provisioning/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "public/app/features/query/components/QueryEditorRow.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -2700,7 +2748,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/support-bundles/SupportBundlesCreate.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] ], "public/app/features/teams/CreateTeam.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -2932,7 +2981,8 @@ exports[`better eslint`] = { ], "public/app/features/variables/query/reducer.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "2"] ], "public/app/features/variables/shared/formatVariable.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -3259,7 +3309,8 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"] + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "5"] ], "public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -3626,7 +3677,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "4"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "5"] ], "public/app/plugins/datasource/zipkin/QueryField.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -3675,10 +3728,17 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] ], + "public/app/plugins/panel/canvas/editor/element/ParamsEditor.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"] + ], "public/app/plugins/panel/canvas/editor/element/PlacementEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/plugins/panel/dashlist/DashList.tsx:5381": [ + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"], + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "1"] + ], "public/app/plugins/panel/debug/CursorView.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3739,9 +3799,6 @@ exports[`better eslint`] = { "public/app/plugins/panel/geomap/layers/basemaps/esri.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/panel/geomap/layers/data/routeLayer.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -3882,7 +3939,7 @@ exports[`better eslint`] = { "public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], "public/app/plugins/panel/table/table-new/cells/ImageCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -4170,6 +4227,164 @@ exports[`no undocumented stories`] = { }` }; +exports[`no skipping a11y tests in stories`] = { + value: `{ + "packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Button/Button.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Carousel/Carousel.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Cascader/Cascader.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/FormField/FormField.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/Checkbox.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/Field.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/FieldArray.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/FieldSet.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/Form.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/InlineField.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Input/Input.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Link/TextLink.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Menu/Menu.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Modal/Modal.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/QueryField/QueryField.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Segment/Segment.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Select/Select.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Select/SelectPerf.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Slider/Slider.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Switch/Switch.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Table/Table.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Tags/TagList.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/Text/Text.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ThemeDemos/Typography.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ], + "packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx:5381": [ + [0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"] + ] + }` +}; + exports[`no gf-form usage`] = { value: `{ "packages/grafana-prometheus/src/components/PromExploreExtraField.tsx:5381": [ diff --git a/.betterer.ts b/.betterer.ts index 61e6153b4dd..fe3b3c7f9f7 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -17,13 +17,35 @@ export default { countEslintErrors() .include('**/*.{ts,tsx}') .exclude(new RegExp(eslintPathsToIgnore.join('|'))), - 'no undocumented stories': () => countUndocumentedStories().include('**/!(*.internal).story.tsx'), + 'no undocumented stories': () => countUndocumentedStories().include('**/*.story.tsx'), + 'no skipping a11y tests in stories': () => countSkippedA11yTestStories().include('**/*.story.tsx'), 'no gf-form usage': () => regexp(/gf-form/gm, 'gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.') .include('**/*.{ts,tsx,html}') .exclude(new RegExp('packages/grafana-ui/src/themes/GlobalStyles')), }; +function countSkippedA11yTestStories() { + return new BettererFileTest(async (filePaths, fileTestResult) => { + await Promise.all( + filePaths.map(async (filePath) => { + // look for skipped a11y tests + const skipRegex = new RegExp("a11y: { test: 'off' }", 'gm'); + + const fileText = await fs.readFile(filePath, 'utf8'); + + const hasSkip = skipRegex.test(fileText); + if (hasSkip) { + // In this case the file contents don't matter: + const file = fileTestResult.addFile(filePath, ''); + // Add the issue to the first character of the file: + file.addIssue(0, 0, 'No skipping of a11y tests in stories. Please fix the component or story instead.'); + } + }) + ); + }); +} + function countUndocumentedStories() { return new BettererFileTest(async (filePaths, fileTestResult) => { await Promise.all( diff --git a/.changelog-archive/CHANGELOG.02.md b/.changelog-archive/CHANGELOG.02.md index 7fc14a683dc..34945530c68 100644 --- a/.changelog-archive/CHANGELOG.02.md +++ b/.changelog-archive/CHANGELOG.02.md @@ -234,7 +234,6 @@ Grunt & Watch tasks: - binary to `/usr/sbin/grafana-server` - init.d script improvements, renamed to `/etc/init.d/grafana-server` - added default file with environment variables, - - `/etc/default/grafana-server` (deb/ubuntu) - `/etc/sysconfig/grafana-server` (centos/redhat) diff --git a/.citools/Variables.mk b/.citools/Variables.mk index 54f102dba73..0f41933b138 100644 --- a/.citools/Variables.mk +++ b/.citools/Variables.mk @@ -12,6 +12,9 @@ $(shell \ endef +# Tool: "air" +air = "$(call compile_tool,air,github.com/air-verse/air)" + # Tool: "bra" bra = "$(call compile_tool,bra,github.com/unknwon/bra)" diff --git a/.citools/src/air/go.mod b/.citools/src/air/go.mod new file mode 100644 index 00000000000..6fd4736b93d --- /dev/null +++ b/.citools/src/air/go.mod @@ -0,0 +1,27 @@ +module air + +go 1.24.4 + +tool github.com/air-verse/air + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/air-verse/air v1.62.0 // indirect + github.com/bep/godartsass/v2 v2.5.0 // indirect + github.com/bep/golibsass v1.2.0 // indirect + github.com/creack/pty v1.1.24 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gohugoio/hugo v0.147.6 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.8.0 // indirect + github.com/tdewolff/parse/v2 v2.8.1 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/.citools/src/air/go.sum b/.citools/src/air/go.sum new file mode 100644 index 00000000000..d733e3519f3 --- /dev/null +++ b/.citools/src/air/go.sum @@ -0,0 +1,45 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/air-verse/air v1.62.0 h1:6CoXL4MAX9dc4xAzLfjMcDfbBoGmW5VjuuTV/1+bI+M= +github.com/air-verse/air v1.62.0/go.mod h1:EO+jWuetL10tS9raffwg8WEV0t0KUeucRRaf9ii86dA= +github.com/bep/godartsass/v2 v2.5.0 h1:tKRvwVdyjCIr48qgtLa4gHEdtRkPF8H1OeEhJAEv7xg= +github.com/bep/godartsass/v2 v2.5.0/go.mod h1:rjsi1YSXAl/UbsGL85RLDEjRKdIKUlMQHr6ChUNYOFU= +github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI= +github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gohugoio/hugo v0.147.6 h1:rL4rnus/5qzj4+FoA+JMzsVvFJ2YZdVIH6pbuCB2P84= +github.com/gohugoio/hugo v0.147.6/go.mod h1:Sb2COQPDPYG+tRSpePtzKytiuVDqkBivEhgIew1QbNo= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= +github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po= +github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= +github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= diff --git a/.citools/src/bra/go.mod b/.citools/src/bra/go.mod index b37f09f62f9..fec1ff55bbb 100644 --- a/.citools/src/bra/go.mod +++ b/.citools/src/bra/go.mod @@ -1,6 +1,6 @@ module bra -go 1.24.4 +go 1.24.5 tool github.com/unknwon/bra diff --git a/.citools/src/cog/go.mod b/.citools/src/cog/go.mod index 44a7a920f35..1b0f1246663 100644 --- a/.citools/src/cog/go.mod +++ b/.citools/src/cog/go.mod @@ -1,6 +1,6 @@ module cog -go 1.24.4 +go 1.24.5 tool github.com/grafana/cog/cmd/cli @@ -42,7 +42,7 @@ require ( github.com/yalue/merged_fs v1.3.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.40.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.33.0 // indirect diff --git a/.citools/src/cog/go.sum b/.citools/src/cog/go.sum index 4a69777748f..513236e89f4 100644 --- a/.citools/src/cog/go.sum +++ b/.citools/src/cog/go.sum @@ -89,8 +89,8 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= diff --git a/.citools/src/cue/go.mod b/.citools/src/cue/go.mod index f59e9af9ff6..d2b53be05ce 100644 --- a/.citools/src/cue/go.mod +++ b/.citools/src/cue/go.mod @@ -1,6 +1,6 @@ module cue -go 1.24.4 +go 1.24.5 tool cuelang.org/go/cmd/cue @@ -27,7 +27,7 @@ require ( github.com/tetratelabs/wazero v1.6.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.40.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect diff --git a/.citools/src/cue/go.sum b/.citools/src/cue/go.sum index 58868acedb9..ff7385db2e7 100644 --- a/.citools/src/cue/go.sum +++ b/.citools/src/cue/go.sum @@ -57,8 +57,8 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= diff --git a/.citools/src/golangci-lint/go.mod b/.citools/src/golangci-lint/go.mod index 9fcd1645750..e990fc28440 100644 --- a/.citools/src/golangci-lint/go.mod +++ b/.citools/src/golangci-lint/go.mod @@ -1,6 +1,6 @@ module golangci-lint -go 1.24.4 +go 1.24.5 tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint diff --git a/.citools/src/jb/go.mod b/.citools/src/jb/go.mod index eec18890a50..05208bc103e 100644 --- a/.citools/src/jb/go.mod +++ b/.citools/src/jb/go.mod @@ -1,6 +1,6 @@ module jb -go 1.24.4 +go 1.24.5 tool github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb diff --git a/.citools/src/lefthook/go.mod b/.citools/src/lefthook/go.mod index ba6572071f8..e2e1c1fb600 100644 --- a/.citools/src/lefthook/go.mod +++ b/.citools/src/lefthook/go.mod @@ -1,6 +1,6 @@ module lefthook -go 1.24.4 +go 1.24.5 tool github.com/evilmartians/lefthook diff --git a/.citools/src/swagger/go.mod b/.citools/src/swagger/go.mod index 344f0c95653..df5684377c8 100644 --- a/.citools/src/swagger/go.mod +++ b/.citools/src/swagger/go.mod @@ -1,6 +1,6 @@ module swagger -go 1.24.4 +go 1.24.5 tool github.com/go-swagger/go-swagger/cmd/swagger diff --git a/.drone.star b/.drone.star index 8cf6438940c..a430359e27d 100644 --- a/.drone.star +++ b/.drone.star @@ -7,7 +7,6 @@ This module returns a Drone configuration including pipelines and secrets. """ -load("scripts/drone/events/cron.star", "cronjobs") load("scripts/drone/events/main.star", "main_pipelines") load("scripts/drone/events/pr.star", "pr_pipelines") load( @@ -37,6 +36,5 @@ def main(_ctx): publish_npm_pipelines() + publish_packages_pipeline() + rgm() + - cronjobs() + secrets() ) diff --git a/.drone.yml b/.drone.yml index bf621c24a1e..345aae7d4bd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -75,7 +75,7 @@ steps: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: lint-starlark trigger: event: @@ -140,7 +140,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -150,7 +150,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -159,7 +159,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -210,7 +210,7 @@ steps: environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-package pull: always volumes: @@ -289,7 +289,7 @@ steps: - commands: - npx wait-on@7.0.1 http://$HOST:$PORT - yarn playwright install --with-deps chromium - - yarn e2e:playwright --grep @plugins + - GRAFANA_URL=http://$HOST:$PORT yarn e2e:playwright --grep @plugins depends_on: - grafana-server - build-test-plugins @@ -472,7 +472,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-cue trigger: event: @@ -542,7 +542,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-cue trigger: branch: main @@ -609,7 +609,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -619,7 +619,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -628,7 +628,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -678,7 +678,7 @@ steps: environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-package pull: always volumes: @@ -761,7 +761,7 @@ steps: - commands: - npx wait-on@7.0.1 http://$HOST:$PORT - yarn playwright install --with-deps chromium - - yarn e2e:playwright --grep @plugins + - GRAFANA_URL=http://$HOST:$PORT yarn e2e:playwright --grep @plugins depends_on: - grafana-server - build-test-plugins @@ -1156,7 +1156,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -1286,7 +1286,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -1427,7 +1427,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket @@ -1519,7 +1519,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - yarn install --immutable || yarn install --immutable @@ -1619,7 +1619,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -1716,7 +1716,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: compile-build-cmd - commands: - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} @@ -1790,7 +1790,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-build pull: always volumes: @@ -1867,7 +1867,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-build pull: always volumes: @@ -1986,7 +1986,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-build pull: always volumes: @@ -2093,7 +2093,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-build pull: always volumes: @@ -2239,7 +2239,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-publish pull: always volumes: @@ -2369,7 +2369,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.4-alpine + image: golang:1.24.5-alpine name: rgm-build pull: always volumes: @@ -2426,367 +2426,6 @@ volumes: - name: github-app temp: {} --- -clone: - retries: 3 -kind: pipeline -name: scan-grafana/grafana:latest-image -platform: - arch: amd64 - os: linux -steps: -- commands: - - echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io - environment: - GCR_CREDENTIALS: - from_secret: gcr_credentials - image: docker:dind - name: authenticate-gcr - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:latest - depends_on: - - authenticate-gcr - image: aquasec/trivy:0.21.0 - name: scan-unknown-low-medium-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:latest - depends_on: - - authenticate-gcr - environment: - GOOGLE_APPLICATION_CREDENTIALS: - from_secret: gcr_credentials_json - image: aquasec/trivy:0.21.0 - name: scan-high-critical-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- image: plugins/slack - name: slack-notify-failure - settings: - channel: grafana-backend-ops - template: 'Nightly docker image scan job for grafana/grafana:latest failed: {{build.link}}' - webhook: - from_secret: slack_webhook_backend - when: - status: failure -trigger: - cron: nightly - event: cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: config - temp: {} ---- -clone: - retries: 3 -kind: pipeline -name: scan-grafana/grafana:main-image -platform: - arch: amd64 - os: linux -steps: -- commands: - - echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io - environment: - GCR_CREDENTIALS: - from_secret: gcr_credentials - image: docker:dind - name: authenticate-gcr - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:main - depends_on: - - authenticate-gcr - image: aquasec/trivy:0.21.0 - name: scan-unknown-low-medium-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:main - depends_on: - - authenticate-gcr - environment: - GOOGLE_APPLICATION_CREDENTIALS: - from_secret: gcr_credentials_json - image: aquasec/trivy:0.21.0 - name: scan-high-critical-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- image: plugins/slack - name: slack-notify-failure - settings: - channel: grafana-backend-ops - template: 'Nightly docker image scan job for grafana/grafana:main failed: {{build.link}}' - webhook: - from_secret: slack_webhook_backend - when: - status: failure -trigger: - cron: nightly - event: cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: config - temp: {} ---- -clone: - retries: 3 -kind: pipeline -name: scan-grafana/grafana:latest-ubuntu-image -platform: - arch: amd64 - os: linux -steps: -- commands: - - echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io - environment: - GCR_CREDENTIALS: - from_secret: gcr_credentials - image: docker:dind - name: authenticate-gcr - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:latest-ubuntu - depends_on: - - authenticate-gcr - image: aquasec/trivy:0.21.0 - name: scan-unknown-low-medium-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:latest-ubuntu - depends_on: - - authenticate-gcr - environment: - GOOGLE_APPLICATION_CREDENTIALS: - from_secret: gcr_credentials_json - image: aquasec/trivy:0.21.0 - name: scan-high-critical-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- image: plugins/slack - name: slack-notify-failure - settings: - channel: grafana-backend-ops - template: 'Nightly docker image scan job for grafana/grafana:latest-ubuntu failed: - {{build.link}}' - webhook: - from_secret: slack_webhook_backend - when: - status: failure -trigger: - cron: nightly - event: cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: config - temp: {} ---- -clone: - retries: 3 -kind: pipeline -name: scan-grafana/grafana:main-ubuntu-image -platform: - arch: amd64 - os: linux -steps: -- commands: - - echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io - environment: - GCR_CREDENTIALS: - from_secret: gcr_credentials - image: docker:dind - name: authenticate-gcr - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:main-ubuntu - depends_on: - - authenticate-gcr - image: aquasec/trivy:0.21.0 - name: scan-unknown-low-medium-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy image --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:main-ubuntu - depends_on: - - authenticate-gcr - environment: - GOOGLE_APPLICATION_CREDENTIALS: - from_secret: gcr_credentials_json - image: aquasec/trivy:0.21.0 - name: scan-high-critical-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- image: plugins/slack - name: slack-notify-failure - settings: - channel: grafana-backend-ops - template: 'Nightly docker image scan job for grafana/grafana:main-ubuntu failed: - {{build.link}}' - webhook: - from_secret: slack_webhook_backend - when: - status: failure -trigger: - cron: nightly - event: cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: config - temp: {} ---- -clone: - retries: 3 -kind: pipeline -name: scan-build-test-and-publish-docker-images -platform: - arch: amd64 - os: linux -steps: -- commands: - - echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io - environment: - GCR_CREDENTIALS: - from_secret: gcr_credentials - image: docker:dind - name: authenticate-gcr - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.24.4-alpine - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22.16.0-alpine - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana-ci-deploy:1.3.3 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.21.3 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM ubuntu:22.04 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM byrnedo/alpine-curl:0.1.8 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM plugins/slack - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM us.gcr.io/kubernetes-dev/package-publish:latest - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/drone-downstream - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docker-puppeteer:1.1.0 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docs-base:latest - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:14.3.2 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM jwilder/dockerize:0.6.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 - depends_on: - - authenticate-gcr - image: aquasec/trivy:0.21.0 - name: scan-unknown-low-medium-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- commands: - - trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli - - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.24.4-alpine - - trivy --exit-code 1 --severity HIGH,CRITICAL node:22.16.0-alpine - - trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm - - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 - - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana-ci-deploy:1.3.3 - - trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.21.3 - - trivy --exit-code 1 --severity HIGH,CRITICAL ubuntu:22.04 - - trivy --exit-code 1 --severity HIGH,CRITICAL byrnedo/alpine-curl:0.1.8 - - trivy --exit-code 1 --severity HIGH,CRITICAL plugins/slack - - trivy --exit-code 1 --severity HIGH,CRITICAL us.gcr.io/kubernetes-dev/package-publish:latest - - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/drone-downstream - - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docker-puppeteer:1.1.0 - - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docs-base:latest - - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:14.3.2 - - trivy --exit-code 1 --severity HIGH,CRITICAL jwilder/dockerize:0.6.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 - depends_on: - - authenticate-gcr - environment: - GOOGLE_APPLICATION_CREDENTIALS: - from_secret: gcr_credentials_json - image: aquasec/trivy:0.21.0 - name: scan-high-critical-vulnerabilities - volumes: - - name: docker - path: /var/run/docker.sock - - name: config - path: /root/.docker/ -- image: plugins/slack - name: slack-notify-failure - settings: - channel: grafana-backend-ops - template: 'Nightly docker image scan job for build-images failed: {{build.link}}' - webhook: - from_secret: slack_webhook_backend - when: - status: failure -trigger: - cron: nightly - event: cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: config - temp: {} ---- get: name: app-id path: ci/data/repo/grafana/grafana/github-app @@ -2986,6 +2625,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: d20f1d6e2e8347701f82114ad352f53db57dc95b5b3831941fa93d063a92b9d8 +hmac: aef043aae7394d3160a7147c8b57599bf1a2f4ba5c596ffb795a0e6a049c73a6 ... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ecd61d20be0..3329738f79c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -84,6 +84,7 @@ /apps/investigations/ @fcjack @matryer @svennergr /apps/advisor/ @grafana/plugins-platform-backend /apps/iam/ @grafana/access-squad +/apps/sdk.mk @grafana/grafana-app-platform-squad /pkg/api/ @grafana/grafana-backend-group /pkg/apis/ @grafana/grafana-app-platform-squad /pkg/apis/query @grafana/grafana-datasources-core-services @@ -150,6 +151,7 @@ /pkg/services/hooks/ @grafana/grafana-backend-group /pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad /pkg/services/licensing/ @grafana/grafana-operator-experience-squad +/pkg/services/mtdsclient/ @grafana/grafana-datasources-core-services /pkg/services/navtree/ @grafana/grafana-backend-group /pkg/services/notifications/ @grafana/grafana-backend-group /pkg/services/org/ @grafana/grafana-backend-group @@ -157,7 +159,7 @@ /pkg/services/preference/ @grafana/grafana-backend-group /pkg/services/provisioning/ @grafana/grafana-search-and-storage /pkg/services/provisioning/alerting/ @grafana/alerting-backend -/pkg/services/query/ @grafana/grafana-app-platform-squad +/pkg/services/query/ @grafana/grafana-datasources-core-services /pkg/services/queryhistory/ @grafana/observability-traces-and-profiling /pkg/services/quota/ @grafana/grafana-search-and-storage /pkg/services/screenshot/ @grafana/grafana-backend-group @@ -178,8 +180,10 @@ /pkg/setting/ @grafana/grafana-backend-services-squad /pkg/tests/ @grafana/grafana-backend-services-squad /pkg/tests/apis/ @grafana/grafana-app-platform-squad -/pkg/tests/apis/query @grafana/grafana-datasources-core-services /pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend +/pkg/tests/apis/features @grafana/grafana-backend-services-squad +/pkg/tests/apis/folder @grafana/grafana-search-and-storage +/pkg/tests/apis/iam @grafana/identity-access-team /pkg/tests/api/correlations/ @grafana/datapro /pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/opentsdb/ @grafana/partner-datasources @@ -408,11 +412,92 @@ /e2e-playwright/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend # Packages -/packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/README.md @grafana/grafana-frontend-platform +/packages/rollup.config.parts.ts @grafana/frontend-ops + +# @grafana/alerting +/packages/grafana-alerting/ @grafana/alerting-frontend + +# @grafana/data +/packages/grafana-data/CHANGELOG.md @grafana/grafana-frontend-platform +/packages/grafana-data/LICENSE_APACHE2 @grafana/grafana-frontend-platform +/packages/grafana-data/README.md @grafana/grafana-frontend-platform +/packages/grafana-data/package.json @grafana/grafana-frontend-platform +/packages/grafana-data/project.json @grafana/grafana-frontend-platform +/packages/grafana-data/rollup.config.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-data/src/internal/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-data/src/unstable.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-data/tsconfig.build.json @grafana/grafana-frontend-platform +/packages/grafana-data/tsconfig.json @grafana/grafana-frontend-platform +/packages/grafana-data/test/ @grafana/grafana-frontend-platform +/packages/grafana-data/typings/ @grafana/grafana-frontend-platform + /packages/grafana-data/src/**/*logs* @grafana/observability-logs +/packages/grafana-data/src/context/plugins/ @grafana/plugins-platform-frontend +/packages/grafana-data/src/dataframe/ @grafana/dataviz-squad +/packages/grafana-data/src/datetime/ @grafana/grafana-frontend-platform +/packages/grafana-data/src/events/ @grafana/grafana-frontend-platform +/packages/grafana-data/src/field/ @grafana/dashboards-squad +/packages/grafana-data/src/geo/ @grafana/dataviz-squad +/packages/grafana-data/src/monaco/ @grafana/partner-datasources +/packages/grafana-data/src/panel/ @grafana/dashboards-squad +/packages/grafana-data/src/query/ @grafana/grafana-datasources-core-services +/packages/grafana-data/src/rbac/ @grafana/access-squad +/packages/grafana-data/src/table/ @grafana/dataviz-squad +/packages/grafana-data/src/text/ @grafana/dataviz-squad +/packages/grafana-data/src/themes/ @grafana/grafana-frontend-platform /packages/grafana-data/src/transformations/ @grafana/datapro +/packages/grafana-data/src/types/ @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/__snapshots__/ @grafanabot +/packages/grafana-data/src/utils/anyToNumber.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/arrayUtils* @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/binaryOperators.ts @grafana/datapro +/packages/grafana-data/src/utils/csv* @grafana/dataviz-squad +/packages/grafana-data/src/utils/dataLinks* @grafana/dashboards-squad +/packages/grafana-data/src/utils/datasource.ts @grafana/grafana-datasources-core-services +/packages/grafana-data/src/utils/docs.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/deprecationWarning* @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/featureToggles.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/fieldParser.ts @grafana/dataviz-squad +/packages/grafana-data/src/utils/flotPairs* @grafana/dataviz-squad +/packages/grafana-data/src/utils/fuzzySearch* @grafana/grafana-search-navigate-organise +/packages/grafana-data/src/utils/labels* @grafana/observability-logs +/packages/grafana-data/src/utils/legend* @grafana/oss-big-tent +/packages/grafana-data/src/utils/location* @grafana/grafana-search-navigate-organise +/packages/grafana-data/src/utils/LocalStorageValueProvider.tsx @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/makeClassES5Compatible.ts @grafana/plugins-platform-frontend +/packages/grafana-data/src/utils/matchPluginId* @grafana/plugins-platform-frontend +/packages/grafana-data/src/utils/namedColorsPalette* @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/nodeGraph.ts @grafana/observability-traces-and-profiling +/packages/grafana-data/src/utils/numbers.ts @grafana/dataviz-squad +/packages/grafana-data/src/utils/object.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/OptionsUIBuilders.ts @grafana/dashboards-squad +/packages/grafana-data/src/utils/Registry* @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/selectUtils.ts @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/series* @grafana/dataviz-squad +/packages/grafana-data/src/utils/store* @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/testdata/ @grafana/dataviz-squad +/packages/grafana-data/src/utils/tests/ @grafana/grafana-frontend-platform +/packages/grafana-data/src/utils/throwIfAngular* @grafana/plugins-platform-frontend +/packages/grafana-data/src/utils/unaryOperators.ts @grafana/datapro +/packages/grafana-data/src/utils/url* @grafana/grafana-search-navigate-organise +/packages/grafana-data/src/utils/valueMappings* @grafana/dashboards-squad +/packages/grafana-data/src/utils/variables.ts @grafana/plugins-platform-frontend +/packages/grafana-data/src/utils/withLoadingIndicator.ts @grafana/access-squad @grafana/alerting-frontend +/packages/grafana-data/src/valueFormats/ @grafana/dashboards-squad +/packages/grafana-data/src/vector/ @grafana/dataviz-squad + +# @grafana/e2e-selectors /packages/grafana-e2e-selectors/ @grafana/grafana-frontend-platform + +# @grafana/eslint-plugin +/packages/grafana-eslint-rules/ @grafana/grafana-frontend-platform + +# @grafana/flamegraph /packages/grafana-flamegraph/ @grafana/observability-traces-and-profiling + +# @grafana/o11y-ds-frontend /packages/grafana-o11y-ds-frontend/ @grafana/observability-logs /packages/grafana-o11y-ds-frontend/src/IntervalInput/ @grafana/observability-traces-and-profiling /packages/grafana-o11y-ds-frontend/src/NodeGraph/ @grafana/observability-traces-and-profiling @@ -421,16 +506,104 @@ /packages/grafana-o11y-ds-frontend/src/TraceToLogs/ @grafana/observability-traces-and-profiling /packages/grafana-o11y-ds-frontend/src/TraceToMetrics/ @grafana/observability-traces-and-profiling /packages/grafana-o11y-ds-frontend/src/TraceToProfiles/ @grafana/observability-traces-and-profiling + +# @grafana/plugin-configs /packages/grafana-plugin-configs/ @grafana/plugins-platform-frontend + +# @grafana/prometheus /packages/grafana-prometheus/ @grafana/oss-big-tent -/packages/grafana-schema/src/**/*canvas* @grafana/dataviz-squad -/packages/grafana-schema/src/**/*tempo* @grafana/observability-traces-and-profiling + +# @grafana/runtime +/packages/grafana-runtime/CHANGELOG.md @grafana/grafana-frontend-platform +/packages/grafana-runtime/LICENSE_APACHE2 @grafana/grafana-frontend-platform +/packages/grafana-runtime/README.md @grafana/grafana-frontend-platform +/packages/grafana-runtime/package.json @grafana/grafana-frontend-platform +/packages/grafana-runtime/project.json @grafana/grafana-frontend-platform +/packages/grafana-runtime/rollup.config.ts @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-runtime/src/internal/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-runtime/src/unstable.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-runtime/tsconfig.build.json @grafana/grafana-frontend-platform +/packages/grafana-runtime/tsconfig.json @grafana/grafana-frontend-platform + +/packages/grafana-runtime/src/analytics @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/analytics/plugins @grafana/plugins-platform-frontend +/packages/grafana-runtime/src/components/DataSourcePicker* @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/components/EmbeddedDashboard.tsx @grafana/dashboards-squad +/packages/grafana-runtime/src/components/FolderPicker.tsx @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/components/PanelRenderer.tsx @grafana/dashboards-squad +/packages/grafana-runtime/src/components/PanelDataErrorView.tsx @grafana/dashboards-squad +/packages/grafana-runtime/src/components/PluginPage.tsx @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/components/QueryEditorWithMigration* @grafana/plugins-platform-frontend @grafana/plugins-platform-backend +/packages/grafana-runtime/src/config.ts @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/services/ @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/services/pluginExtensions @grafana/plugins-platform-frontend +/packages/grafana-runtime/src/services/CorrelationsService.ts @grafana/datapro +/packages/grafana-runtime/src/services/LocationService.test.tsx @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/services/LocationService.tsx @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/services/LocationSrv.ts @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/services/live.ts @grafana/dashboards-squad +/packages/grafana-runtime/src/utils/chromeHeaderHeight.ts @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/utils/DataSourceWithBackend* @grafana/grafana-datasources-core-services +/packages/grafana-runtime/src/utils/licensing.ts @grafana/grafana-operator-experience-squad +/packages/grafana-runtime/src/utils/logging.ts @grafana/grafana-frontend-platform +/packages/grafana-runtime/src/utils/migrationHandler* @grafana/plugins-platform-frontend @grafana/plugins-platform-backend +/packages/grafana-runtime/src/utils/plugin.ts @grafana/plugins-platform-frontend +/packages/grafana-runtime/src/utils/publicDashboardQueryHandler.ts @grafana/grafana-operator-experience-squad +/packages/grafana-runtime/src/utils/queryResponse* @grafana/grafana-datasources-core-services +/packages/grafana-runtime/src/utils/rbac.ts @grafana/identity-access-team +/packages/grafana-runtime/src/utils/returnToPrevious.ts @grafana/grafana-search-navigate-organise +/packages/grafana-runtime/src/utils/toDataQueryError.ts @grafana/grafana-datasources-core-services +/packages/grafana-runtime/src/utils/userStorage* @grafana/plugins-platform-frontend @grafana/grafana-frontend-platform + +# @grafana/schema +/packages/grafana-schema/ @grafana/grafana-app-platform-squad + +/packages/grafana-schema/src/**/annotationslist @grafana/dashboards-squad +/packages/grafana-schema/src/**/azuremonitor @grafana/partner-datasources +/packages/grafana-schema/src/**/barchart @grafana/dataviz-squad +/packages/grafana-schema/src/**/bargauge @grafana/dataviz-squad +/packages/grafana-schema/src/**/candlestick @grafana/dataviz-squad +/packages/grafana-schema/src/**/canvas @grafana/dataviz-squad +/packages/grafana-schema/src/**/cloudwatch @grafana/aws-datasources +/packages/grafana-schema/src/**/dashboard @grafana/dashboards-squad +/packages/grafana-schema/src/**/dashboardlist @grafana/grafana-search-navigate-organise +/packages/grafana-schema/src/**/datagrid @grafana/dataviz-squad +/packages/grafana-schema/src/**/debug @ryantxu +/packages/grafana-schema/src/**/elasticsearch @grafana/partner-datasources +/packages/grafana-schema/src/**/gauge @grafana/dataviz-squad +/packages/grafana-schema/src/**/geomap @grafana/dataviz-squad +/packages/grafana-schema/src/**/googlecloudmonitoring @grafana/partner-datasources +/packages/grafana-schema/src/**/grafanapyroscope @grafana/observability-traces-and-profiling +/packages/grafana-schema/src/**/heatmap @grafana/dataviz-squad +/packages/grafana-schema/src/**/histogram @grafana/dataviz-squad +/packages/grafana-schema/src/**/librarypanel @grafana/sharing-squad +/packages/grafana-schema/src/**/logs @grafana/observability-logs +/packages/grafana-schema/src/**/logsnew @grafana/observability-logs +/packages/grafana-schema/src/**/loki @grafana/oss-big-tent @grafana/observability-logs +/packages/grafana-schema/src/**/news @grafana/dataviz-squad +/packages/grafana-schema/src/**/nodegraph @grafana/observability-traces-and-profiling @grafana/app-o11y-visualizations +/packages/grafana-schema/src/**/parca @grafana/oss-big-tent +/packages/grafana-schema/src/**/piechart @grafana/dataviz-squad +/packages/grafana-schema/src/**/publicdashboard @grafana/sharing-squad +/packages/grafana-schema/src/**/stat @grafana/dataviz-squad +/packages/grafana-schema/src/**/statetimeline @grafana/dataviz-squad +/packages/grafana-schema/src/**/statushistory @grafana/dataviz-squad +/packages/grafana-schema/src/**/table @grafana/dataviz-squad +/packages/grafana-schema/src/**/tempo @grafana/observability-traces-and-profiling +/packages/grafana-schema/src/**/text @grafana/dataviz-squad +/packages/grafana-schema/src/**/timeseries @grafana/dataviz-squad +/packages/grafana-schema/src/**/trend @grafana/dataviz-squad +/packages/grafana-schema/src/**/xychart @grafana/dataviz-squad +/packages/grafana-schema/src/**/xychart2 @grafana/dataviz-squad + +# @grafana/sql /packages/grafana-sql/ @grafana/partner-datasources @grafana/oss-big-tent -/packages/grafana-ui/.storybook/ @grafana/grafana-frontend-platform -/packages/grafana-ui/src/components/ @grafana/grafana-frontend-platform + +# @grafana/ui +/packages/grafana-ui/ @grafana/grafana-frontend-platform /packages/grafana-ui/src/components/BarGauge/ @grafana/dataviz-squad /packages/grafana-ui/src/components/DataLinks/ @grafana/dataviz-squad -/packages/grafana-ui/src/components/DateTimePickers/ @grafana/grafana-frontend-platform /packages/grafana-ui/src/components/Gauge/ @grafana/dataviz-squad /packages/grafana-ui/src/components/PluginSignatureBadge/ @grafana/plugins-platform-frontend /packages/grafana-ui/src/components/Sparkline/ @grafana/grafana-frontend-platform @grafana/app-o11y-visualizations @@ -445,9 +618,13 @@ /packages/grafana-ui/src/graveyard/Graph/ @grafana/dataviz-squad /packages/grafana-ui/src/graveyard/GraphNG/ @grafana/dataviz-squad /packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad -/packages/grafana-ui/src/utils/storybook/ @grafana/grafana-frontend-platform -/packages/grafana-alerting/ @grafana/alerting-frontend +/packages/grafana-ui/tsconfig.build.json @grafana/frontend-ops +/packages/grafana-ui/tsconfig.json @grafana/frontend-ops + +# @grafana/i18n /packages/grafana-i18n/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend + +# @grafana/test-utils /packages/grafana-test-utils @grafana/grafana-frontend-platform # root files, mostly frontend @@ -825,6 +1002,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/publish-technical-documentation-release.yml @grafana/docs-tooling /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/storybook-a11y.yml @grafana/grafana-frontend-platform /.github/workflows/storybook-verification.yml @grafana/grafana-frontend-platform /.github/workflows/storybook-verification-playwright.yml @grafana/grafana-frontend-platform /.github/workflows/update-make-docs.yml @grafana/docs-tooling diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index b8864e985e2..e2c3a6c3b89 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -47,6 +47,10 @@ runs: - '.github/actions/checkout/**' - '**/go.mod' - '**/go.sum' + - 'go.mod' + - 'go.sum' + - 'go.work' + - 'go.work.sum' - '**.go' - 'pkg/**' - '!pkg/**.md' diff --git a/.github/issue-opened.json b/.github/issue-opened.json index 276c9464a76..cfb39512bc5 100644 --- a/.github/issue-opened.json +++ b/.github/issue-opened.json @@ -4,8 +4,7 @@ "memberOf": { "org": "grafana" }, - "noLabels": true, - "addLabel": "internal", + "noLabels": true, "comment": " please add one or more appropriate labels. Here are some tips:\r\n\r\n- if you are making an issue, TODO, or reminder for yourself or your team, please add one label that best describes the product or feature area. Please also add the issue to your project board. :rocket:\r\n\r\n- if you are making an issue for any other reason (docs typo, you found a bug, etc), please add at least one label that best describes the product or feature that you are discussing (e.g. `area/alerting`, `datasource/loki`, `type/docs`, `type/bug`, etc). [Our issue triage](https://github.com/grafana/grafana/blob/main/contribute/ISSUE_TRIAGE.md#3-categorize-an-issue) doc also provides additional guidance on labeling. :rocket:\r\n\r\n Thank you! :heart:" } ] \ No newline at end of file diff --git a/.github/renovate.json5 b/.github/renovate.json5 index aa28c25a240..37d44d74483 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -12,6 +12,7 @@ 'react-refresh', "@types/history", // this can be removed entirely when we upgrade history since v5 exposes types directly + "cypress", // cypress use is deprecated, we should not bump it anymore "history", // we should bump this together with react-router-dom (see https://github.com/grafana/grafana/issues/76744) "react-router", // we should bump this together with history and react-router-dom "react-router-dom", // we should bump this together with history (see https://github.com/grafana/grafana/issues/76744) @@ -31,29 +32,41 @@ automerge: true, matchCurrentVersion: "!/^0/", matchUpdateTypes: ["patch"], - matchPackageNames: ["!/^@?storybook/", "!/^@locker/"], + matchPackageNames: ["!/^@?storybook/", "!/^@locker/", "!/^@grafana/"], + minimumReleaseAge: "7 days", + }, + { + automerge: true, + matchCurrentVersion: "!/^0/", + matchUpdateTypes: ["patch"], + matchPackageNames: ["/^@grafana/"], }, { extends: ["schedule:monthly"], groupName: "Storybook updates", matchPackageNames: ["/^@?storybook/"], rangeStrategy: "bump", + minimumReleaseAge: "7 days", }, { groupName: "React Aria", matchPackageNames: ["@react-aria/{/,}**", "@react-stately/{/,}**"], + minimumReleaseAge: "7 days", }, { groupName: "Moveable", matchPackageNames: ["moveable", "react-moveable"], + minimumReleaseAge: "7 days", }, { groupName: "Slate", matchPackageNames: ["@types/slate", "@types/slate-react", "slate", "slate-react"], + minimumReleaseAge: "7 days", }, { groupName: "d3", matchPackageNames: ["d3{/,}**", "@types/d3{/,}**"], + minimumReleaseAge: "7 days", }, { groupName: "scenes", @@ -66,21 +79,25 @@ { groupName: "visx", matchPackageNames: ["@visx/{/,}**"], + minimumReleaseAge: "7 days", }, { groupName: "uLibraries", matchPackageNames: ["@leeoniya/**", "uplot"], reviewers: ["leeoniya"], + minimumReleaseAge: "7 days", }, { groupName: "locker", reviewers: ["team:grafana/plugins-platform-frontend"], matchPackageNames: ["@locker/{/,}**"], + minimumReleaseAge: "7 days", }, { groupName: "augurs", matchPackageNames: ["@bsull/augurs"], reviewers: ["sd2k"], + minimumReleaseAge: "7 days", }, { "matchDepTypes": ["devDependencies"], diff --git a/.github/workflows/add-to-whats-new.yml b/.github/workflows/add-to-whats-new.yml index 699c6b7a220..a6a8bb8ea2e 100644 --- a/.github/workflows/add-to-whats-new.yml +++ b/.github/workflows/add-to-whats-new.yml @@ -10,7 +10,7 @@ jobs: permissions: pull-requests: write steps: - - uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 + - uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 with: message: | Since you've added the `Add to what's new` label, consider drafting a [What's new note](https://admin.grafana.com/content-admin/#/collections/whats-new/new) for this feature. diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 85a35c35d67..ea7c12b4428 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -271,7 +271,7 @@ jobs: # Comment on the PR - name: Comment on PR if: steps.levitate-run.outputs.exit_code == 1 - uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 with: header: levitate-breaking-change-comment number: ${{ github.event.pull_request.number }} @@ -288,7 +288,7 @@ jobs: # Remove comment from the PR (no more breaking changes) - name: Remove comment from PR if: steps.levitate-run.outputs.exit_code == 0 - uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 with: header: levitate-breaking-change-comment number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index 97694bd2d04..80b853d0c69 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -117,3 +117,41 @@ jobs: } env: SLACK_WEBHOOK_URL: ${{ env.AUTOTRIAGER_SLACK_WEBHOOK_URL }} + auto-label-internal-issues: + needs: [main] + if: github.repository == 'grafana/grafana' + runs-on: ubuntu-latest + permissions: + issues: write + id-token: write + steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] + with: + # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_triager path in Vault + repo_secrets: | + GITHUB_APP_ID=plugins_platform_issue_triager_github_bot:app_id + GITHUB_APP_PRIVATE_KEY=plugins_platform_issue_triager_github_bot:app_pem + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + with: + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} + permission-members: read + - name: Check if member of grafana org + id: check-if-grafana-org-member + continue-on-error: true + run: gh api https://api.github.com/orgs/grafana/members/${{ env.ACTOR }} >/dev/null 2>&1 && echo "is_grafana_org_member=true" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + ACTOR: ${{ github.actor }} + - name: "Auto label internal issues" + if: steps.check-if-grafana-org-member.outputs.is_grafana_org_member == 'true' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' + run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: internal \ No newline at end of file diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 5e7d116d0c2..215a4016344 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -286,6 +286,7 @@ jobs: /home/bench/tests/playwright-results.json - name: Upload HTML report + id: upload-html uses: actions/upload-artifact@v4 with: name: playwright-html-${{ github.run_number }} @@ -304,10 +305,13 @@ jobs: - name: Show test results env: FAILED: ${{ steps.check-jobs.outputs.any-failed }} + REPORT_URL: ${{ steps.upload-html.outputs.artifact-url }} # sed removes the leading `../../src/` from the paths run: | - npx playwright merge-reports --reporter list ./all-blob-reports | sed 's|\(\.\./\)\{1,\}src/|/|g' + npx playwright merge-reports --reporter list ./blobs | sed 's|\(\.\./\)\{1,\}src/|/|g' if [ "$FAILED" = "true" ]; then + echo "" + echo "Download the test report from $REPORT_URL" exit 1 fi diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index f7b148ac83d..91dc3037f11 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -4,18 +4,31 @@ on: workflow_dispatch: pull_request: branches: [main] - paths: - - .github/workflows/pr-go-workspace-check.yml - - go.mod - - go.sum - - go.work - - go.work.sum - - '**/go.mod' - - '**/go.sum' - - '**.go' + +permissions: {} jobs: - check: + detect-changes: + name: Detect whether code changed + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.backend }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/pr-go-workspace-check.yml + + check-workspace: + needs: detect-changes + if: needs.detect-changes.outputs.changed == 'true' name: Go Workspace Check runs-on: ubuntu-latest @@ -47,6 +60,8 @@ jobs: run: ./scripts/go-workspace/validate-dockerfile.sh check-wire: + needs: detect-changes + if: needs.detect-changes.outputs.changed == 'true' name: Check Wire Changes runs-on: ubuntu-latest @@ -114,3 +129,29 @@ jobs: fi exit 1 fi + + # This is the job that is actually required by rulesets. + # We want to only require one job instead of all the individual tests. + # Future work also allows us to start skipping some tests based on changed files. + required-go-workspace-check: + needs: + - check-workspace + - check-wire + # always() is the best function here. + # success() || failure() will skip this function if any need is also skipped. + # That means conditional jobs will fail the entire requirement check. + if: always() + + name: All Go Workspace Checks complete + runs-on: ubuntu-latest + steps: + - name: Check the checks + env: + NEEDS: ${{ toJson(needs) }} + run: | + FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')" + echo "$FAILURES" + if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then + exit 1 + fi + echo "All OK!" diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 35b8a219d62..fda8533dc0e 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -1,6 +1,10 @@ name: Build Release Packages on: workflow_dispatch: + schedule: + # Every weeknight at midnight + # "Scheduled workflows will only run on the default branch." (docs.github.com) + - cron: "0 0 * * 1-5" push: branches: - release-*.*.* @@ -96,8 +100,8 @@ jobs: with: github-token: ${{ steps.generate_token.outputs.token }} script: | - const {REF, VERSION, BUILD_ID, BUCKET, GRAFANA_COMMIT} = process.env; - + const {REF, VERSION, BUILD_ID, BUCKET, GRAFANA_COMMIT, GITHUB_EVENT_NAME} = process.env; + await github.rest.actions.createWorkflowDispatch({ owner: 'grafana', repo: 'grafana-enterprise', @@ -108,6 +112,7 @@ jobs: "build-id": String(BUILD_ID), "bucket": BUCKET, "grafana-commit": GRAFANA_COMMIT, + "source-event": GITHUB_EVENT_NAME, } }) diff --git a/.github/workflows/storybook-a11y.yml b/.github/workflows/storybook-a11y.yml new file mode 100644 index 00000000000..f666ba37b95 --- /dev/null +++ b/.github/workflows/storybook-a11y.yml @@ -0,0 +1,57 @@ +name: Run Storybook a11y tests +on: + pull_request: + push: + branches: + - main + - release-*.*.* + +permissions: {} + +jobs: + detect-changes: + name: Detect whether code changed + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.frontend }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/storybook-a11y.yml + + test-storybook-a11y: + runs-on: ubuntu-latest-8-cores + permissions: + contents: read + id-token: write + needs: detect-changes + if: needs.detect-changes.outputs.changed == 'true' + name: "Run Storybook a11y tests" + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + - run: yarn install --immutable --check-cache + - name: Install Playwright browsers + run: npx playwright install --with-deps + - name: Start Storybook + run: yarn storybook & + - name: Run tests + # the chromium browser used by Playwright sets its locale to "en_US@posix" by default + # this is not a valid language code, and causes some stories to fail to load! + # instead, we set the LANG environment variable to en_US to override this + # see https://github.com/microsoft/playwright/issues/34046 + env: + LANG: en_US + run: npx wait-on --timeout 120000 http://localhost:9001 && yarn test:storybook diff --git a/CHANGELOG.md b/CHANGELOG.md index 345aedc792a..1a979b3b88e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,313 @@ + + +# 12.1.0 (2025-07-23) + +### Features and enhancements + +- **Access:** Disable role none option if advanced access control is not enabled [#107378](https://github.com/grafana/grafana/pull/107378), [@Jguer](https://github.com/Jguer) +- **Alerting:** Add OAuth2 Support for Webhook Receiver [#106302](https://github.com/grafana/grafana/pull/106302), [@JacobsonMT](https://github.com/JacobsonMT) +- **Alerting:** Add ability to import rules to GMA from Prometheus YAML [#105807](https://github.com/grafana/grafana/pull/105807), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Add details to the payload when tracking import to GMA [#106404](https://github.com/grafana/grafana/pull/106404), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Add export folder action to the new list view [#106256](https://github.com/grafana/grafana/pull/106256), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Add filters for health and contact point in Prometheus Rules api [#106580](https://github.com/grafana/grafana/pull/106580), [@moustafab](https://github.com/moustafab) +- **Alerting:** Add loading spinner for loading groups state [#106289](https://github.com/grafana/grafana/pull/106289), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Add need more info for import ui datasource field [#106364](https://github.com/grafana/grafana/pull/106364), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Add provenance to Prometheus API [#106596](https://github.com/grafana/grafana/pull/106596), [@moustafab](https://github.com/moustafab) +- **Alerting:** Add provenance to remote-ruler extension response (Enterprise) +- **Alerting:** Add simplified routing metadata to the details tab [#106403](https://github.com/grafana/grafana/pull/106403), [@gillesdemey](https://github.com/gillesdemey) +- **Alerting:** Add state history backend to write ALERTS metric [#104361](https://github.com/grafana/grafana/pull/104361), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Add support for Redis Sentinel for Alerting HA [#106322](https://github.com/grafana/grafana/pull/106322), [@vstpme](https://github.com/vstpme) +- **Alerting:** Allow disabling recording rules write for a data source in the UI [#106664](https://github.com/grafana/grafana/pull/106664), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Correctly persist FiredAt in SyncRuleStatePersister [#106658](https://github.com/grafana/grafana/pull/106658), [@fayzal-g](https://github.com/fayzal-g) +- **Alerting:** Ensure errors cleared when Alerting after error [#105246](https://github.com/grafana/grafana/pull/105246), [@moustafab](https://github.com/moustafab) +- **Alerting:** Evaluate all imported from Prometheus rules sequentially [#106295](https://github.com/grafana/grafana/pull/106295), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Extensible Settings module [#107831](https://github.com/grafana/grafana/pull/107831), [@konrad147](https://github.com/konrad147) +- **Alerting:** Filter out rules managed by integrations and add an info alert [#106602](https://github.com/grafana/grafana/pull/106602), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Filter out synthetic datasource-managed rules when importing to GMA [#106358](https://github.com/grafana/grafana/pull/106358), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** List V2 - Add labels popup [#107193](https://github.com/grafana/grafana/pull/107193), [@konrad147](https://github.com/konrad147) +- **Alerting:** List V2 - Grouped view filters [#106400](https://github.com/grafana/grafana/pull/106400), [@konrad147](https://github.com/konrad147) +- **Alerting:** List V2 - Use backend filters for GMA rules [#106897](https://github.com/grafana/grafana/pull/106897), [@konrad147](https://github.com/konrad147) +- **Alerting:** Make paginated rules endpoint strongly consistent (Enterprise) +- **Alerting:** Optimize out unnecessary permission check for rule groups (Enterprise) +- **Alerting:** Optimize prometheus api permission checks [#106299](https://github.com/grafana/grafana/pull/106299), [@moustafab](https://github.com/moustafab) +- **Alerting:** Optimize prometheus api permission checks (Enterprise) +- **Alerting:** Persist alert instance FiredAt field [#105927](https://github.com/grafana/grafana/pull/105927), [@fayzal-g](https://github.com/fayzal-g) +- **Alerting:** Remove ruler from alert list view2 [#106778](https://github.com/grafana/grafana/pull/106778), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Resend alerts for states that are missing in the eval results [#105965](https://github.com/grafana/grafana/pull/105965), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Send notifications immediately on Error|NoData -> Normal transitions [#106421](https://github.com/grafana/grafana/pull/106421), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Support PDC in Grafana-managed recording rules [#106677](https://github.com/grafana/grafana/pull/106677), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Use default_datasource_uid as the default target for recording rules in UI [#106415](https://github.com/grafana/grafana/pull/106415), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Annotations:** Use dashboard uids instead of dashboard ids [#106676](https://github.com/grafana/grafana/pull/106676), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **App Platform Provisioning:** Add experimental nanogit mode for Git Sync [#106763](https://github.com/grafana/grafana/pull/106763), [@MissingRoberto](https://github.com/MissingRoberto) +- **Auth:** Add Azure/Entra workload identity support [#104807](https://github.com/grafana/grafana/pull/104807), [@mehighlow](https://github.com/mehighlow) +- **Auth:** Enable improved session handling by default for OAuth and SAML [#107442](https://github.com/grafana/grafana/pull/107442), [@mgyongyosi](https://github.com/mgyongyosi) +- **Auth:** Enable ssoSettingsLDAP by default [#106310](https://github.com/grafana/grafana/pull/106310), [@mgyongyosi](https://github.com/mgyongyosi) +- **Auth:** Remove api key endpoints [#106019](https://github.com/grafana/grafana/pull/106019), [@dmihai](https://github.com/dmihai) +- **Auth:** Remove code for authenticating API keys [#105998](https://github.com/grafana/grafana/pull/105998), [@dmihai](https://github.com/dmihai) +- **Azure:** Support scope selection in Resource Graph queries [#105835](https://github.com/grafana/grafana/pull/105835), [@aangelisc](https://github.com/aangelisc) +- **Betterer:** Only allow singleton Storage use [#105310](https://github.com/grafana/grafana/pull/105310), [@tskarhed](https://github.com/tskarhed) +- **Caching:** Remove memcached reconnect_interval setting (Enterprise) +- **Chore:** Update k8s.io to v0.33.1 [#105307](https://github.com/grafana/grafana/pull/105307), [@ryantxu](https://github.com/ryantxu) +- **Cloud Monitoring:** Add support for service account impersonation [#107022](https://github.com/grafana/grafana/pull/107022), [@zoltanbedi](https://github.com/zoltanbedi) +- **CloudMigrations:** Add Mute Timings as dependency for Notification Policies [#106751](https://github.com/grafana/grafana/pull/106751), [@macabu](https://github.com/macabu) +- **CloudWatch:** Backport aws-sdk-go-v2 update from external plugin [#107136](https://github.com/grafana/grafana/pull/107136), [@njvrzm](https://github.com/njvrzm) +- **CloudWatch:** Improve instance attribute variable query editor [#105206](https://github.com/grafana/grafana/pull/105206), [@iwysiu](https://github.com/iwysiu) +- **Cloudwatch:** Add missing AWS regions [#106304](https://github.com/grafana/grafana/pull/106304), [@chriscerie](https://github.com/chriscerie) +- **Dashboard Provisioning:** Reduce db load [#106114](https://github.com/grafana/grafana/pull/106114), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Dashboard:** Add Alert icon in library panels [#107723](https://github.com/grafana/grafana/pull/107723), [@axelavargas](https://github.com/axelavargas) +- **Dashboard:** Add server-configurable quick ranges for the time picker [#102254](https://github.com/grafana/grafana/pull/102254), [@chodges15](https://github.com/chodges15) +- **Dashboard:** Formatting Currency - add new custom 'financial' currency format without abbreviations [#106604](https://github.com/grafana/grafana/pull/106604), [@axelavargas](https://github.com/axelavargas) +- **Dashboard:** Library Panels - Add ability to search by folder name [#106997](https://github.com/grafana/grafana/pull/106997), [@axelavargas](https://github.com/axelavargas) +- **Dashboard:** Schema V2 - Auto-transform V2 dashboards in V1Resource export mode [#105997](https://github.com/grafana/grafana/pull/105997), [@axelavargas](https://github.com/axelavargas) +- **Datasources:** Migrate to new sigv4 middleware (Enterprise) +- **Datasources:** Update grafana-aws-sdk for new sigv4 middleware and aws-sdk-go v1 removal [#107522](https://github.com/grafana/grafana/pull/107522), [@njvrzm](https://github.com/njvrzm) +- **DatePicker:** Add cursor not-allowed style and hover background color [#106451](https://github.com/grafana/grafana/pull/106451), [@ywzheng1](https://github.com/ywzheng1) +- **Dependencies:** Bump Go to v1.24.4 [#106533](https://github.com/grafana/grafana/pull/106533), [@macabu](https://github.com/macabu) +- **Dependencies:** Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 [#107379](https://github.com/grafana/grafana/pull/107379), [@macabu](https://github.com/macabu) +- **Dependencies:** Bump github.com/openfga/openfga to v1.8.13 to address CVE-2025-48371 [#106064](https://github.com/grafana/grafana/pull/106064), [@macabu](https://github.com/macabu) +- **ElasticSearch:** Remove frontend response parsing [#104148](https://github.com/grafana/grafana/pull/104148), [@nojaf](https://github.com/nojaf) +- **Geomap:** Add HiDPI support to CARTO basemap (#81195) [#106211](https://github.com/grafana/grafana/pull/106211), [@cledwynl](https://github.com/cledwynl) +- **Git Sync UI:** Delete Provisioned Dashboard Flow [#106593](https://github.com/grafana/grafana/pull/106593), [@ywzheng1](https://github.com/ywzheng1) +- **Grafana/data:** Extract fuzzy search core [#107110](https://github.com/grafana/grafana/pull/107110), [@Clarity-89](https://github.com/Clarity-89) +- **I18n:** Update eslint rule to catch some untranslated object properties [#105072](https://github.com/grafana/grafana/pull/105072), [@tomratcliffe](https://github.com/tomratcliffe) +- **InfluxDB:** Add an optional time range filter for tag queries in the query panel autocompleteInflux tag filter [#107195](https://github.com/grafana/grafana/pull/107195), [@NikolayTsvetkov](https://github.com/NikolayTsvetkov) +- **LBAC for data sources:** Adds team filtering for lbac rules (Enterprise) +- **Library Panels:** Mark library panel RBAC as GA & enable by default [#106833](https://github.com/grafana/grafana/pull/106833), [@kaydelaney](https://github.com/kaydelaney) +- **Library Panels:** Modify connection api endpoint to be compatible with unified storage [#107088](https://github.com/grafana/grafana/pull/107088), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Library elements:** Remove ability to set as "library variable" [#106594](https://github.com/grafana/grafana/pull/106594), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Library panels:** Remove `libraryPanelRBAC` feature flag, and enable rbac by default [#107222](https://github.com/grafana/grafana/pull/107222), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Loki:** Remove experimental lokiQuerySplittingConfig [#107298](https://github.com/grafana/grafana/pull/107298), [@ivanahuckova](https://github.com/ivanahuckova) +- **Loki:** Remove experimental predefined operations [#107289](https://github.com/grafana/grafana/pull/107289), [@ivanahuckova](https://github.com/ivanahuckova) +- **OAuth:** Add access token as third source for user info extraction [#107636](https://github.com/grafana/grafana/pull/107636), [@Jguer](https://github.com/Jguer) +- **Plugin Extensions:** Expose PluginMeta generic in usePluginContext [#107577](https://github.com/grafana/grafana/pull/107577), [@MattIPv4](https://github.com/MattIPv4) +- **Postgres:** Switch the datasource plugin from lib/pq to pgx [#103961](https://github.com/grafana/grafana/pull/103961), [@zoltanbedi](https://github.com/zoltanbedi) +- **Preferences:** Use dashboard uid for the home dashboard [#106666](https://github.com/grafana/grafana/pull/106666), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Profiles:** Stop passing response headers for Grafana-Pyroscope and parca datasources [#106577](https://github.com/grafana/grafana/pull/106577), [@simonswine](https://github.com/simonswine) +- **Prometheus:** Deprecation message for Azure auth [#106490](https://github.com/grafana/grafana/pull/106490), [@bossinc](https://github.com/bossinc) +- **Prometheus:** Facilitate tree shaking with exports and bundler mode [#105575](https://github.com/grafana/grafana/pull/105575), [@NWRichmond](https://github.com/NWRichmond) +- **Prometheus:** Migrate remaining selectors to data-testid [#106564](https://github.com/grafana/grafana/pull/106564), [@idastambuk](https://github.com/idastambuk) +- **ProvisionedFolder:** Delete folder drawer [#107089](https://github.com/grafana/grafana/pull/107089), [@ywzheng1](https://github.com/ywzheng1) +- **Provisioning:** Add pure git repository type [#106815](https://github.com/grafana/grafana/pull/106815), [@MissingRoberto](https://github.com/MissingRoberto) +- **Querying:** Pass dashboard and panel title as headers [#107032](https://github.com/grafana/grafana/pull/107032), [@ivanahuckova](https://github.com/ivanahuckova) +- **Remote Alertmanager:** Send SMTP config [#106337](https://github.com/grafana/grafana/pull/106337), [@santihernandezc](https://github.com/santihernandezc) +- **Restore dashboards:** Add filters and search [#106994](https://github.com/grafana/grafana/pull/106994), [@Clarity-89](https://github.com/Clarity-89) +- **SCIM:** Ignore unsupported fields in user PATCH requests (Enterprise) +- **SCIM:** Implement operation for adding an externalId value to a team (Enterprise) +- **SCIM:** Implement the add members operation in group PATCH requests (Enterprise) +- **SCIM:** Implement the remove members operation in group PATCH requests (Enterprise) +- **SCIM:** Update externalId field in group PATCH request (Enterprise) +- **SQL Expressions:** Always convert on type first [#106083](https://github.com/grafana/grafana/pull/106083), [@kylebrandt](https://github.com/kylebrandt) +- **Select:** Set min width for the current selected item when width=auto [#106131](https://github.com/grafana/grafana/pull/106131), [@tskarhed](https://github.com/tskarhed) +- **StateTimeline:** Display false and empty string values [#107059](https://github.com/grafana/grafana/pull/107059), [@jesdavpet](https://github.com/jesdavpet) +- **StateTimeline:** Support `NaN` and `null` value mappings [#105638](https://github.com/grafana/grafana/pull/105638), [@fastfrwrd](https://github.com/fastfrwrd) +- **Storage:** Take `migration_locking` setting into account [#105938](https://github.com/grafana/grafana/pull/105938), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **TableNG:** Refactor to better take advantage of react-data-grid [#103755](https://github.com/grafana/grafana/pull/103755), [@leeoniya](https://github.com/leeoniya) +- **Tables:** Pills for Table Cells [#107485](https://github.com/grafana/grafana/pull/107485), [@timlevett](https://github.com/timlevett) +- **Teams:** Add support for updating externalId field [#106406](https://github.com/grafana/grafana/pull/106406), [@dmihai](https://github.com/dmihai) +- **Tempo:** Enable native histograms for Tempo service graph [#105989](https://github.com/grafana/grafana/pull/105989), [@bohandley](https://github.com/bohandley) +- **TimeRangePicker:** Highlight range on hover [#106616](https://github.com/grafana/grafana/pull/106616), [@joshhunt](https://github.com/joshhunt) +- **TraceView:** Resource attributes links extension point [#104680](https://github.com/grafana/grafana/pull/104680), [@edvard-falkskar](https://github.com/edvard-falkskar) +- **Transformations:** Add "Auto" mode to Organize Fields [#103055](https://github.com/grafana/grafana/pull/103055), [@gelicia](https://github.com/gelicia) +- **Transformations:** GA the Regression transformation [#106074](https://github.com/grafana/grafana/pull/106074), [@gelicia](https://github.com/gelicia) +- **Unified storage:** Respect GF_DATABASE_URL override [#105331](https://github.com/grafana/grafana/pull/105331), [@pstibrany](https://github.com/pstibrany) +- **VQB:** Add selected columns to GROUP BY dropdown (#106349) [#106391](https://github.com/grafana/grafana/pull/106391), [@Shubham19032004](https://github.com/Shubham19032004) +- **VQB:** Allow custom table names in TableSelector [#106420](https://github.com/grafana/grafana/pull/106420), [@Victorthedev](https://github.com/Victorthedev) +- **XYChart:** Add support for x=time [#106459](https://github.com/grafana/grafana/pull/106459), [@leeoniya](https://github.com/leeoniya) + +### Bug fixes + +- **Alerting:** Fix $value type when single data source is queried [#106080](https://github.com/grafana/grafana/pull/106080), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Fix ImportToGMARules flaky test [#106495](https://github.com/grafana/grafana/pull/106495), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Fix RefIds not being shown when creating or editing Grafana-managed recording rule [#106840](https://github.com/grafana/grafana/pull/106840), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) +- **Alerting:** Fix contact points tab visibility when user can only create [#106735](https://github.com/grafana/grafana/pull/106735), [@JacobsonMT](https://github.com/JacobsonMT) +- **Alerting:** Fix eval time unit in list view [#106488](https://github.com/grafana/grafana/pull/106488), [@ebuildy](https://github.com/ebuildy) +- **Alerting:** Fix group interval override when adding new rules [#107324](https://github.com/grafana/grafana/pull/107324), [@konrad147](https://github.com/konrad147) +- **Alerting:** Fix group-level labels and query_offset in the import API [#106379](https://github.com/grafana/grafana/pull/106379), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Fix notification policy conflicts originating from provenance mismatch [#107343](https://github.com/grafana/grafana/pull/107343), [@moustafab](https://github.com/moustafab) +- **Alerting:** Fix resolved notifications for same-label Error to Normal transitions [#106210](https://github.com/grafana/grafana/pull/106210), [@alexander-akhmetov](https://github.com/alexander-akhmetov) +- **Alerting:** Hide labels section if we only have private labels [#105996](https://github.com/grafana/grafana/pull/105996), [@gillesdemey](https://github.com/gillesdemey) +- **Annotations:** Remove prometheus from legacy runner [#106737](https://github.com/grafana/grafana/pull/106737), [@scottlepp](https://github.com/scottlepp) +- **Azure:** Fix Application Insights metadata requests [#105614](https://github.com/grafana/grafana/pull/105614), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix duplicated trace links [#105698](https://github.com/grafana/grafana/pull/105698), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix legend formatting [#106504](https://github.com/grafana/grafana/pull/106504), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix resource name determination in template variable queries [#105705](https://github.com/grafana/grafana/pull/105705), [@aangelisc](https://github.com/aangelisc) +- **BarChart/StateTimeline:** Use noValue setting for error message when data is empty [#107147](https://github.com/grafana/grafana/pull/107147), [@fastfrwrd](https://github.com/fastfrwrd) +- **CloudWatch:** Fix http client handling + assume role bug [#107893](https://github.com/grafana/grafana/pull/107893), [@njvrzm](https://github.com/njvrzm) +- **CloudWatch:** Fix proxy transport issue [#107807](https://github.com/grafana/grafana/pull/107807), [@njvrzm](https://github.com/njvrzm) +- **Dashboard:** FF `dashboardNewLayouts` Fix library panels non-editable when multiple added [#107052](https://github.com/grafana/grafana/pull/107052), [@axelavargas](https://github.com/axelavargas) +- **Dashboard:** Fix cache validation to prevent stale cache [#105918](https://github.com/grafana/grafana/pull/105918), [@yashschandra](https://github.com/yashschandra) +- **Dashboard:** Fixes issue with dashboard links that include all variables [#106356](https://github.com/grafana/grafana/pull/106356), [@torkelo](https://github.com/torkelo) +- **Dashboards:** Fix history list for dashboard uids that end in `-` [#107073](https://github.com/grafana/grafana/pull/107073), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Drilldown:** Fix js crash when using http [#105646](https://github.com/grafana/grafana/pull/105646), [@chu121su12](https://github.com/chu121su12) +- **Fix:** Increase login_attempt.ip_address column length for IPv6 support [#107035](https://github.com/grafana/grafana/pull/107035), [@Jguer](https://github.com/Jguer) +- **FlameGraph:** Fix bug for function names that conflict with JavaScript object prototype properties [#106338](https://github.com/grafana/grafana/pull/106338), [@simonswine](https://github.com/simonswine) +- **Folders:** Correctly resolve nested folder breadcrumbs [#106344](https://github.com/grafana/grafana/pull/106344), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **GrafanaUI:** Fix Combobox ignoring loading prop [#105584](https://github.com/grafana/grafana/pull/105584), [@ValeraS](https://github.com/ValeraS) +- **Graphite:** Fix annotation queries [#106553](https://github.com/grafana/grafana/pull/106553), [@aangelisc](https://github.com/aangelisc) +- **Graphite:** Fix date mutation [#107414](https://github.com/grafana/grafana/pull/107414), [@aangelisc](https://github.com/aangelisc) +- **Graphite:** Fix nested variable interpolation for repeated rows [#106976](https://github.com/grafana/grafana/pull/106976), [@aangelisc](https://github.com/aangelisc) +- **K8s:** Dashboards /apis: Fix library element connections [#106734](https://github.com/grafana/grafana/pull/106734), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Loki:** Fix health check message [#107170](https://github.com/grafana/grafana/pull/107170), [@wooffie](https://github.com/wooffie) +- **Loki:** Fix issue where step parameter using a template variable was marked as invalid [#106541](https://github.com/grafana/grafana/pull/106541), [@ivanahuckova](https://github.com/ivanahuckova) +- **Loki:** Fix label browser not sorted after selection of a label [#107394](https://github.com/grafana/grafana/pull/107394), [@paulojmdias](https://github.com/paulojmdias) +- **Org:** Fix org deletion [#106193](https://github.com/grafana/grafana/pull/106193), [@stephaniehingtgen](https://github.com/stephaniehingtgen) +- **Plugins:** Fix and encode invalid gRPC header values [#107339](https://github.com/grafana/grafana/pull/107339), [@ivanahuckova](https://github.com/ivanahuckova) +- **PostgreSQL:** Fix error on panel when toggling sqlDatasourceDatabaseSelection feature [#106965](https://github.com/grafana/grafana/pull/106965), [@HasithDeAlwis](https://github.com/HasithDeAlwis) +- **Profiles:** Fix for passing the response headers [#106293](https://github.com/grafana/grafana/pull/106293), [@simonswine](https://github.com/simonswine) +- **Reporting:** Stop sending reports with Never schedule on creation (Enterprise) +- **SCIM:** Fix PUT request for deactivating a user (Enterprise) +- **SCIM:** Fix the removal of all members in group PUT requests (Enterprise) +- **SCIM:** Fix user patch operation (Enterprise) +- **Security:** Add fix for CVE-2025-3580 [#105976](https://github.com/grafana/grafana/pull/105976), [@baldm0mma](https://github.com/baldm0mma) +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108333](https://github.com/grafana/grafana/pull/108333), [@mgyongyosi](https://github.com/mgyongyosi) +- **Settings:** Fix reencryption and rollback of encrypted values in setting table (Enterprise) +- **Tempo:** Fix showing dangling edges in NodeGraph [#107245](https://github.com/grafana/grafana/pull/107245), [@ifrost](https://github.com/ifrost) +- **ToolTip:** Fix flexbox bug with tooltip when `maxWidth` is set manually [#107145](https://github.com/grafana/grafana/pull/107145), [@jdmarshall](https://github.com/jdmarshall) +- **URLParams:** Stringify true values as key=true always (fixes issues with variables with true value) [#106440](https://github.com/grafana/grafana/pull/106440), [@torkelo](https://github.com/torkelo) + +### Breaking changes + +- **Alerting:** Enable recording rules by default [#105603](https://github.com/grafana/grafana/pull/105603), [@alexander-akhmetov](https://github.com/alexander-akhmetov) + +### Plugin development fixes & changes + +- **Carousel:** Always center image [#106468](https://github.com/grafana/grafana/pull/106468), [@ashharrison90](https://github.com/ashharrison90) +- **Drawer:** Include divider and close button when passing a custom title element [#106896](https://github.com/grafana/grafana/pull/106896), [@ashharrison90](https://github.com/ashharrison90) + + + + +# 12.0.3 (2025-07-23) + +### Bug fixes + +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108280](https://github.com/grafana/grafana/pull/108280), [@volcanonoodle](https://github.com/volcanonoodle) + + + + +# 11.6.4 (2025-07-23) + +### Features and enhancements + +- **Dependencies:** Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 [#107555](https://github.com/grafana/grafana/pull/107555), [@macabu](https://github.com/macabu) +- **StateTimeline:** Add endTime to tooltip [#107605](https://github.com/grafana/grafana/pull/107605), [@adela-almasan](https://github.com/adela-almasan) +- **Unified storage:** Respect GF_DATABASE_URL override [#107573](https://github.com/grafana/grafana/pull/107573), [@pstibrany](https://github.com/pstibrany) + +### Bug fixes + +- **Alerting:** Fix group interval override when adding new rules [#107496](https://github.com/grafana/grafana/pull/107496), [@konrad147](https://github.com/konrad147) +- **Azure:** Fix legend formatting [#106934](https://github.com/grafana/grafana/pull/106934), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix resource name determination in template variable queries [#106939](https://github.com/grafana/grafana/pull/106939), [@aangelisc](https://github.com/aangelisc) +- **Graphite:** Fix annotation queries [#106940](https://github.com/grafana/grafana/pull/106940), [@aangelisc](https://github.com/aangelisc) +- **Graphite:** Fix date mutation [#107523](https://github.com/grafana/grafana/pull/107523), [@aangelisc](https://github.com/aangelisc) +- **Graphite:** Fix nested variable interpolation for repeated rows [#107564](https://github.com/grafana/grafana/pull/107564), [@aangelisc](https://github.com/aangelisc) +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108281](https://github.com/grafana/grafana/pull/108281), [@volcanonoodle](https://github.com/volcanonoodle) + + + + +# 11.5.7 (2025-07-23) + +### Features and enhancements + +- **Dependencies:** Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 [#107556](https://github.com/grafana/grafana/pull/107556), [@macabu](https://github.com/macabu) + +### Bug fixes + +- **Azure:** Fix legend formatting [#106933](https://github.com/grafana/grafana/pull/106933), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix resource name determination in template variable queries [#106938](https://github.com/grafana/grafana/pull/106938), [@aangelisc](https://github.com/aangelisc) +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108280](https://github.com/grafana/grafana/pull/108280), [@volcanonoodle](https://github.com/volcanonoodle) + + + + +# 11.4.7 (2025-07-23) + +### Features and enhancements + +- **Dependencies:** Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 [#107557](https://github.com/grafana/grafana/pull/107557), [@macabu](https://github.com/macabu) + +### Bug fixes + +- **Azure:** Fix legend formatting [#106932](https://github.com/grafana/grafana/pull/106932), [@aangelisc](https://github.com/aangelisc) +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108279](https://github.com/grafana/grafana/pull/108279), [@volcanonoodle](https://github.com/volcanonoodle) + + + + +# 11.3.9 (2025-07-23) + +### Features and enhancements + +- **Dependencies:** Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 [#107558](https://github.com/grafana/grafana/pull/107558), [@macabu](https://github.com/macabu) + +### Bug fixes + +- **Azure:** Fix legend formatting [#106931](https://github.com/grafana/grafana/pull/106931), [@aangelisc](https://github.com/aangelisc) +- **Security:** Fixes for CVE-2025-6197 and CVE-2025-6023 [#108278](https://github.com/grafana/grafana/pull/108278), [@volcanonoodle](https://github.com/volcanonoodle) + + + + +# 12.0.2+security-01 (2025-07-17) + +### Features and enhancements + +- **Profiles:** Stop passing response headers for Grafana-Pyroscope and parca datasources [#106730](https://github.com/grafana/grafana/pull/106730), [@simonswine](https://github.com/simonswine) + +### Bug fixes + +- **FlameGraph:** Fix bug for function names that conflict with JavaScript object prototype properties [#106625](https://github.com/grafana/grafana/pull/106625), [@simonswine](https://github.com/simonswine) +- **Security:** Fixed CVE-2025-6023 +- **Security:** Fixed CVE-2025-6197 + + + + +# 11.6.3+security-01 (2025-07-17) + +### Features and enhancements + +- **Profiles:** Stop passing response headers for Grafana-Pyroscope and parca datasources [#106729](https://github.com/grafana/grafana/pull/106729), [@simonswine](https://github.com/simonswine) + +### Bug fixes + +- **FlameGraph:** Fix bug for function names that conflict with JavaScript object prototype properties [#106624](https://github.com/grafana/grafana/pull/106624), [@simonswine](https://github.com/simonswine) +- **Security:** Fixed CVE-2025-6023 +- **Security:** Fixed CVE-2025-6197 + + + + +# 11.5.6+security-01 (2025-07-17) + +### Bug fixes + +- **Security:** Fixed CVE-2025-6023 +- **Security:** Fixed CVE-2025-6197 + + + + +# 11.4.6+security-01 (2025-07-17) + +### Bug fixes + +- **Security:** Fixed CVE-2025-6023 +- **Security:** Fixed CVE-2025-6197 + + + + +# 11.3.8+security-01 (2025-07-17) + +### Bug fixes + +- **Security:** Fixed CVE-2025-6023 +- **Security:** Fixed CVE-2025-6197 + + # 12.0.2 (2025-06-17) diff --git a/Dockerfile b/Dockerfile index 07874fff677..ddc832d65c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ARG JS_SRC=js-builder # By using FROM instructions we can delegate dependency updates to dependabot FROM alpine:3.21.3 AS alpine-base FROM ubuntu:22.04 AS ubuntu-base -FROM golang:1.24.4-alpine AS go-builder-base +FROM golang:1.24.5-alpine AS go-builder-base FROM --platform=${JS_PLATFORM} node:22-alpine AS js-builder-base # Javascript build stage diff --git a/GOVERNANCE.md b/GOVERNANCE.md index ff129a87557..f246ca03386 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -7,7 +7,6 @@ This document describes the rules and governance of the project. It is meant to - **Maintainers**: Maintainers lead an individual project or parts thereof ([`MAINTAINERS.md`][maintainers]). - **Projects**: A single repository in the Grafana GitHub organization and listed below is referred to as a project: - - clock-panel - devtools - gel-app diff --git a/Makefile b/Makefile index e2778594804..1d058780a48 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ include .bingo/Variables.mk include .citools/Variables.mk GO = go -GO_VERSION = 1.24.4 +GO_VERSION = 1.24.5 GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh) GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh) SH_FILES ?= $(shell find ./scripts -name *.sh) @@ -168,7 +168,7 @@ gen-cuev2: ## Do all CUE code generation # TODO (@radiohead): uncomment once we want to start generating code for all apps. # For now, we want to use an explicit list of apps to generate code for. # -# APPS_DIRS=$(shell find ./apps -mindepth 1 -maxdepth 1 -type d | sort) +#APPS_DIRS=$(shell find ./apps -type d -exec test -f "{}/Makefile" \; -print | sort) APPS_DIRS := ./apps/dashboard ./apps/folder ./apps/alerting/notifications .PHONY: gen-apps @@ -263,8 +263,8 @@ build-plugin-go: ## Build decoupled plugins build: build-go build-js ## Build backend and frontend. .PHONY: run -run: ## Build and run web server on filesystem changes. See /.bra.toml for configuration. - $(bra) run +run: ## Build and run backend, and watch for changes. See .air.toml for configuration. + $(air) -c .air.toml .PHONY: run-go run-go: ## Build and run web server immediately. @@ -275,9 +275,9 @@ run-go: ## Build and run web server immediately. run-frontend: deps-js ## Fetch js dependencies and watch frontend for rebuild yarn start -.PHONY: run-air -run-air: ## [Experimental] Build and run backend, and watch for changes. See .air.toml for configuration. Check https://github.com/air-verse/air for installation instructions. - air -c .air.toml +.PHONY: run-bra +run-air: ## [Deprecated] Build and run web server on filesystem changes. See /.bra.toml for configuration. + $(bra) run .PHONY: frontend-service-check frontend-service-check: diff --git a/apps/advisor/Makefile b/apps/advisor/Makefile index b8b335bfa68..aaac06377ca 100644 --- a/apps/advisor/Makefile +++ b/apps/advisor/Makefile @@ -1,22 +1,5 @@ -APP_SDK_VERSION := v0.39.2 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) +include ../sdk.mk .PHONY: generate -generate: ## Run Grafana App SDK code generation - @$(APP_SDK_BIN) generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none +generate: install-app-sdk update-app-sdk + @$(APP_SDK_BIN) generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none --useoldmanifestkinds diff --git a/apps/advisor/README.md b/apps/advisor/README.md index 27c44711f43..c7929d9db62 100644 --- a/apps/advisor/README.md +++ b/apps/advisor/README.md @@ -142,7 +142,6 @@ Check [`security_config_step.go`](./pkg/app/checks/configchecks/security_config_ 2. **Type Safety**: Use type assertions to ensure you're working with the correct type of item. 3. **Severity Levels**: Use appropriate severity levels: - - `CheckReportFailureSeverityHigh`: For critical issues that need immediate attention - `CheckReportFailureSeverityLow`: For non-critical issues that can be addressed later diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 65832aabb4b..853c9850196 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -1,89 +1,286 @@ module github.com/grafana/grafana/apps/advisor -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 - k8s.io/apimachinery v0.33.2 - k8s.io/klog/v2 v2.130.1 + github.com/Masterminds/semver/v3 v3.4.0 + github.com/google/go-github/v70 v70.0.0 + github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 + github.com/grafana/grafana v0.0.0-00010101000000-000000000000 + github.com/grafana/grafana-app-sdk v0.40.0 + github.com/grafana/grafana-app-sdk/logging v0.39.3 + github.com/grafana/grafana-plugin-sdk-go v0.278.0 + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2 + github.com/stretchr/testify v1.10.0 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) +// transitive dependencies that need replaced +// TODO: stop depending on grafana core +replace github.com/grafana/grafana => ../.. + +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 + require ( + cloud.google.com/go/compute/metadata v0.7.0 // indirect + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/apache/arrow-go/v18 v18.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/at-wat/mqtt-go v0.19.4 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect + github.com/aws/smithy-go v1.22.4 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bluele/gcache v0.0.2 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v1.0.0 // indirect + github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dlmiddlecote/sqlstats v1.0.2 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect + github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect + github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/elazarl/goproxy v1.7.2 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gchaincl/sqlhooks v1.3.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-ldap/ldap/v3 v3.4.4 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-migrate/migrate/v4 v4.7.0 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/google/wire v0.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b // indirect + github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/dataplane/sdata v0.0.9 // indirect + github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect + github.com/grafana/grafana-aws-sdk v1.0.4 // indirect + github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // indirect + github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/sqlds/v4 v4.2.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/invopop/yaml v0.3.1 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/jaegertracing/jaeger-idl v0.5.0 // indirect + github.com/jessevdk/go-flags v1.5.0 // indirect + github.com/jmespath-community/go-jmespath v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lestrrat-go/strftime v1.0.4 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/magefile/mage v1.15.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mithrandie/csvq v1.18.1 // indirect + github.com/mithrandie/csvq-driver v1.7.0 // indirect + github.com/mithrandie/go-file/v2 v2.1.0 // indirect + github.com/mithrandie/go-text v1.6.0 // indirect + github.com/mithrandie/ternary v1.1.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openfga/openfga v1.8.13 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/open-feature/go-sdk v1.14.1 // indirect + github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 // indirect + github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/alertmanager v0.28.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tetratelabs/wazero v1.8.2 // indirect + github.com/tjhop/slog-gokit v0.1.3 // indirect + github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect + github.com/unknwon/com v1.0.1 // indirect + github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect + github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.mongodb.org/mongo-driver v1.16.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect - golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect - golang.org/x/time v0.9.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.34.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + gonum.org/v1/gonum v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect + gopkg.in/telebot.v3 v3.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.2 // indirect + k8s.io/api v0.33.3 // indirect k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/client-go v0.33.2 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/client-go v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect + xorm.io/builder v0.3.6 // indirect ) diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 90b34992392..2613bb8c90c 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -1,341 +1,1918 @@ +cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= +cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= +cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= +github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= +github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= +github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= +github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/apache/arrow-go/v18 v18.3.0 h1:Xq4A6dZj9Nu33sqZibzn012LNnewkTUlfKVUFD/RX/I= +github.com/apache/arrow-go/v18 v18.3.0/go.mod h1:eEM1DnUTHhgGAjf/ChvOAQbUQ+EPohtDrArffvUjPg8= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/at-wat/mqtt-go v0.19.4 h1:R2cbCU7O5PHQ38unbe1Y51ncG3KsFEJV6QeipDoqdLQ= +github.com/at-wat/mqtt-go v0.19.4/go.mod h1:AsiWc9kqVOhqq7LzUeWT/AkKUBfx3Sw5cEe8lc06fqA= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= +github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 h1:6VFPH/Zi9xYFMJKPQOX5URYkQoXRWeJ7V/7Y6ZDYoms= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= +github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4= +github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk= +github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY= +github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= +github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= +github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= +github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= +github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= +github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= +github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= +github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= +github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= +github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= +github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o= +github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4= +github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= +github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= +github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= +github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= +github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= +github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= +github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= +github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= +github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs= +github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ= +github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II= +github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE= +github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8= +github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE= +github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms= +github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g= +github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y= +github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk= +github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= +github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM= +github.com/blugelabs/bluge v0.2.2/go.mod h1:am1LU9jS8dZgWkRzkGLQN3757EgMs3upWrU2fdN9foE= +github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo= +github.com/blugelabs/bluge_segment_api v0.2.0/go.mod h1:95XA+ZXfRj/IXADm7gZ+iTcWOJPg5jQTY1EReIzl3LA= +github.com/blugelabs/ice v1.0.0 h1:um7wf9e6jbkTVCrOyQq3tKK43fBMOvLUYxbj3Qtc4eo= +github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2Lo+IbQ= +github.com/blugelabs/ice/v2 v2.0.1 h1:mzHbntLjk2v7eDRgoXCgzOsPKN1Tenu9Svo6l9cTLS4= +github.com/blugelabs/ice/v2 v2.0.1/go.mod h1:QxAWSPNwZwsIqS25c3lbIPFQrVvT1sphf5x5DfMLH5M= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= +github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= +github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= +github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8= +github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= +github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI= +github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxWhbByHgQSCUQvY= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAttAqWaudUAsM9iHASi/4eFBK+qn4qeaNto7g8bK4= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= -github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= -github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= +github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= +github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= +github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= +github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= +github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= +github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb h1:oTl2j6/4miQUYmXANp2pBuYCWA5f8NVYFfCWpczpFso= -github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= -github.com/grafana/grafana-app-sdk v0.30.0 h1:Hqn2pETu2mQ4RpWkZYEQfu01P7xd1Z1Gj+HX/8aB0tw= -github.com/grafana/grafana-app-sdk v0.30.0/go.mod h1:jhfqNIovb+Mes2vdMf9iMCWQkp1GTNtyNuExONtiNuk= -github.com/grafana/grafana-app-sdk v0.31.0/go.mod h1:Xw00NL7qpRLo5r3Gn48Bl1Xn2n4eUDI5pYf/wMufKWs= -github.com/grafana/grafana-app-sdk v0.35.1/go.mod h1:Zx5MkVppYK+ElSDUAR6+fjzOVo6I/cIgk+ty+LmNOxI= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4= -github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM= -github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM= -github.com/grafana/grafana-app-sdk/logging v0.35.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b h1:mfUAq/N+mS82EcE35hDXWtfVY7UhTjzZxzssvFt9tvQ= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= +github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg= +github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= +github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= +github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= +github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= +github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b h1:31MwoIKKT9Ay0ZjbT4lkfcPijiWogUWzXs2EjrCgodI= +github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:dLtYBp1pza5HYalezNvzlP8JDeKrZ5BKTonDgEOE0NY= +github.com/grafana/grafana/apps/secret v0.0.0-20250711114246-c9b2126c4ad5 h1:+fMhUoqwGdY8ntH0GL2icJa3uk+bTiIMicawDG2r9Uc= +github.com/grafana/grafana/apps/secret v0.0.0-20250711114246-c9b2126c4ad5/go.mod h1:TIrKvhgo2j6lvVeOZ3TUmXbI4I48d6v7QcadL/f6SKQ= +github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b h1:ei01IFqmnXkOrrVvsT3CYe+i5xYra3SCX7Wsu3PMsDU= +github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:+H4Va9jDJlGQJjAN+OFD/hLx2I/yEzDRMQLaKecvgAc= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2 h1:lvmcK9XOJUJiYhl2kH4nwAKOUdq+ug+ueIGqfKlip3E= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2/go.mod h1:3ZgUe0E3rIhI026xF4DKFptOst/jpDHJ/Sn+bRODzI4= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:6OKkPWDB8PetDXqMVMOWL35iTCEUdpATwwpuew0k8+o= +github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= +github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250627191313-2f1a6ae1712b h1:m78RNSvTseSpvwQYe5HcdsiADSw4vj6+QUppuCL63gw= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:mu3yl0GxB0eQZV1q7Kka0pkF3Th9x7W04WrjR9wqBlc= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 h1:oJnbhG6ZNy10AjsgNeAtAKeGHogIGOMfAsBH6fYYa5M= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= +github.com/grafana/sqlds/v4 v4.2.3 h1:9ibD1c5O5u9fifEkBSig+jAc41TUEz+M+bWQqDsofP4= +github.com/grafana/sqlds/v4 v4.2.3/go.mod h1:bv+XHabfUF4xkgg4y+nYFCK8rpMHZsMaQk56qNaJcAM= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/api v1.31.2 h1:NicObVJHcCmyOIl7Z9iHPvvFrocgTYo9cITSGg0/7pw= +github.com/hashicorp/consul/api v1.31.2/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= +github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc= +github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= +github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= +github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mithrandie/csvq v1.18.1 h1:f7NB2scbb7xx2ffPduJ2VtZ85RpWXfvanYskAkGlCBU= +github.com/mithrandie/csvq v1.18.1/go.mod h1:MRJj7AtcXfk7jhNGxLuJGP3LORmh4lpiPWxQ7VyCRn8= +github.com/mithrandie/csvq-driver v1.7.0 h1:ejiavXNWwTPMyr3fJFnhcqd1L1cYudA0foQy9cZrqhw= +github.com/mithrandie/csvq-driver v1.7.0/go.mod h1:HcN3xL9UCJnBYA/AIQOOB/KlyfXAiYr5yxDmiwrGk5o= +github.com/mithrandie/go-file/v2 v2.1.0 h1:XA5Tl+73GXMDvgwSE3Sg0uC5FkLr3hnXs8SpUas0hyg= +github.com/mithrandie/go-file/v2 v2.1.0/go.mod h1:9YtTF3Xo59GqC1Pxw6KyGVcM/qubAMlxVsqI/u9r++c= +github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyWO6s= +github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to= +github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4= +github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= +github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= +github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/openfga/openfga v1.8.12 h1:xEirA6tFwaJfjBDtbHWCK0/Tw+B8XleRyhg9dcEpzHo= -github.com/openfga/openfga v1.8.12/go.mod h1:fIZyekdNB+tWQ6zIiglZonAc5ErZiDGMeHue/BzRYRM= -github.com/openfga/openfga v1.8.13 h1:ROURkotKhbmtyBX3188+cNElN8AOZmTl0CMkxUqwawo= -github.com/openfga/openfga v1.8.13/go.mod h1:h1VGcVW81eY1YyDtFx5+gxxAIEhIiOGR9SRGgs/X/k8= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/open-feature/go-sdk v1.14.1 h1:jcxjCIG5Up3XkgYwWN5Y/WWfc6XobOhqrIwjyDBsoQo= +github.com/open-feature/go-sdk v1.14.1/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 h1:6jpO63NCEZv4xunJj+aNlDuFVuRkVBPMcIuxvFPYRWQ= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3/go.mod h1:dPUHjAIFzg+ci/wt6XxlNiiMkOh5Yw4SGyeRY0AFT0g= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 h1:ZdqlGnNwhWf3luhBQlIpbglvcCzjkcuEgOEhYhr5Emc= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5/go.mod h1:jrD4UG3ZCzuwImKHlyuIN2iWeYjlOX5+zJ/sX45efuE= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= +github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= +github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= +github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= +github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= +go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= +go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= +go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= +go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= -go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw= +gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d h1:H8tOf8XM88HvKqLTxe755haY6r1fqqzLbEnfrmLXlSA= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= +google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= +google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= +gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= -k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kms v0.33.3 h1:7cQWC+GSH211NgY8LRKjBXNtkzra5SkpYzeZrOt5D+8= +k8s.io/kms v0.33.3/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= -sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= +xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= +xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= diff --git a/apps/advisor/kinds/check.cue b/apps/advisor/kinds/check.cue index 2ce51faf910..01982443ffc 100644 --- a/apps/advisor/kinds/check.cue +++ b/apps/advisor/kinds/check.cue @@ -4,19 +4,17 @@ check: { kind: "Check" pluralName: "Checks" current: "v0alpha1" + validation: { + operations: [ + "CREATE", + "UPDATE", + ] + } versions: { "v0alpha1": { codegen: { - backend: true - // Frontend code is generated following the instructions from - // https://github.com/grafana/grafana/blob/main/public/app/api/README.md - frontend: false - } - validation: { - operations: [ - "CREATE", - "UPDATE", - ] + ts: {enabled: false} + go: {enabled: true} } schema: { #Data: { diff --git a/apps/advisor/kinds/checktype.cue b/apps/advisor/kinds/checktype.cue index e2d653e629e..315d231754d 100644 --- a/apps/advisor/kinds/checktype.cue +++ b/apps/advisor/kinds/checktype.cue @@ -7,8 +7,8 @@ checktype: { versions: { "v0alpha1": { codegen: { - frontend: true - backend: true + ts: {enabled: false} + go: {enabled: true} } schema: { #Step: { diff --git a/apps/advisor/pkg/apis/advisor_manifest.go b/apps/advisor/pkg/apis/advisor_manifest.go index eb596a274e2..a85ff8b904f 100644 --- a/apps/advisor/pkg/apis/advisor_manifest.go +++ b/apps/advisor/pkg/apis/advisor_manifest.go @@ -8,6 +8,7 @@ package apis import ( "encoding/json" "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -27,14 +28,16 @@ var ( var appManifestData = app.ManifestData{ AppName: "advisor", Group: "advisor.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Check", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", + Kind: "Check", + Plural: "Checks", + Scope: "Namespaced", + Conversion: false, Admission: &app.AdmissionCapabilities{ Validation: &app.ValidationCapability{ Operations: []app.AdmissionOperation{ @@ -45,17 +48,13 @@ var appManifestData = app.ManifestData{ }, Schema: &versionSchemaCheckv0alpha1, }, - }, - }, - { - Kind: "CheckType", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", - Schema: &versionSchemaCheckTypev0alpha1, + Kind: "CheckType", + Plural: "CheckTypes", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaCheckTypev0alpha1, }, }, }, @@ -81,3 +80,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go index 9e382c5315f..92fe70fe192 100644 --- a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go +++ b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go @@ -102,6 +102,12 @@ func (r *Runner) Run(ctx context.Context) error { } else { lastCreated = time.Now() } + } else { + // Run an initial cleanup to remove old checks + err = r.cleanupChecks(ctxWithoutCancel, logger) + if err != nil { + logger.Error("Error cleaning up old check reports", "error", err) + } } } @@ -132,17 +138,35 @@ func (r *Runner) Run(ctx context.Context) error { } } +func (r *Runner) listChecks(ctx context.Context, logger logging.Logger) ([]resource.Object, error) { + list, err := r.client.List(ctx, r.namespace, resource.ListOptions{}) + if err != nil { + return nil, err + } + + checks := list.GetItems() + for list.GetContinue() != "" { + logger.Debug("List has continue token, listing next page", "continue", list.GetContinue()) + list, err = r.client.List(ctx, r.namespace, resource.ListOptions{Continue: list.GetContinue()}) + if err != nil { + return nil, err + } + checks = append(checks, list.GetItems()...) + } + return checks, nil +} + // checkLastCreated returns the creation time of the last check created // regardless of its ID. This assumes that the checks are created in batches // so a batch will have a similar creation time. // In case it finds an unprocessed check from a previous run, it will set it to error. func (r *Runner) checkLastCreated(ctx context.Context, log logging.Logger) (time.Time, error) { - list, err := r.client.List(ctx, r.namespace, resource.ListOptions{}) + checkList, err := r.listChecks(ctx, log) if err != nil { return time.Time{}, err } lastCreated := time.Time{} - for _, item := range list.GetItems() { + for _, item := range checkList { itemCreated := item.GetCreationTimestamp().Time if itemCreated.After(lastCreated) { lastCreated = itemCreated @@ -209,14 +233,16 @@ func (r *Runner) createChecks(ctx context.Context, logger logging.Logger) error // cleanupChecks deletes the olders checks if the number of checks exceeds the limit. func (r *Runner) cleanupChecks(ctx context.Context, logger logging.Logger) error { - list, err := r.client.List(ctx, r.namespace, resource.ListOptions{Limit: -1}) + checkList, err := r.listChecks(ctx, logger) if err != nil { return err } + logger.Debug("Cleaning up checks", "numChecks", len(checkList)) + // organize checks by type checksByType := map[string][]resource.Object{} - for _, check := range list.GetItems() { + for _, check := range checkList { labels := check.GetLabels() checkType, ok := labels[checks.TypeLabel] if !ok { @@ -226,8 +252,10 @@ func (r *Runner) cleanupChecks(ctx context.Context, logger logging.Logger) error checksByType[checkType] = append(checksByType[checkType], check) } - for _, checks := range checksByType { + for checkType, checks := range checksByType { + logger.Debug("Checking checks", "checkType", checkType, "numChecks", len(checks)) if len(checks) > r.maxHistory { + logger.Debug("Deleting old checks", "checkType", checkType, "maxHistory", r.maxHistory, "numChecks", len(checks)) // Sort checks by creation time sort.Slice(checks, func(i, j int) bool { ti := checks[i].GetCreationTimestamp().Time @@ -242,6 +270,7 @@ func (r *Runner) cleanupChecks(ctx context.Context, logger logging.Logger) error if err != nil { return fmt.Errorf("error deleting check: %w", err) } + logger.Debug("Deleted check", "check", check.GetStaticMetadata().Identifier()) } } } diff --git a/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go b/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go index 69b4e809b4c..de522be7adc 100644 --- a/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go +++ b/apps/advisor/pkg/app/checkscheduler/checkscheduler_test.go @@ -103,6 +103,69 @@ func TestRunner_checkLastCreated_UnprocessedCheck(t *testing.T) { assert.Equal(t, expectedAnnotations, patchOperation.Value) } +func TestRunner_checkLastCreated_PaginatedResponse(t *testing.T) { + // Create checks with different creation times + past := time.Now().Add(-1 * time.Hour) + now := time.Now() + + mockClient := &MockClient{ + listFunc: func(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { + if options.Continue == "" { + // First page - return oldest and middle checks with continue token + return &advisorv0alpha1.CheckList{ + ListMeta: metav1.ListMeta{ + Continue: "continue-token-123", + }, + Items: []advisorv0alpha1.Check{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "check-1", + CreationTimestamp: metav1.NewTime(past), + Annotations: map[string]string{ + checks.StatusAnnotation: "completed", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "check-2", + CreationTimestamp: metav1.NewTime(past), + Annotations: map[string]string{ + checks.StatusAnnotation: "completed", + }, + }, + }, + }, + }, nil + } + // Second page - verify continue token is passed and return newest check + assert.Equal(t, "continue-token-123", options.Continue) + return &advisorv0alpha1.CheckList{ + Items: []advisorv0alpha1.Check{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "check-3", + CreationTimestamp: metav1.NewTime(now), + Annotations: map[string]string{ + checks.StatusAnnotation: "completed", + }, + }, + }, + }, + }, nil + }, + } + + runner := &Runner{ + client: mockClient, + log: &logging.NoOpLogger{}, + } + + lastCreated, err := runner.checkLastCreated(context.Background(), &logging.NoOpLogger{}) + assert.NoError(t, err) + assert.Equal(t, now.Truncate(time.Second), lastCreated.Truncate(time.Second)) +} + func TestRunner_createChecks_ErrorOnCreate(t *testing.T) { mockCheckService := &MockCheckService{checks: []checks.Check{&mockCheck{id: "check-1"}}} diff --git a/apps/advisor/pkg/app/utils.go b/apps/advisor/pkg/app/utils.go index cf090b67260..80f71cc679e 100644 --- a/apps/advisor/pkg/app/utils.go +++ b/apps/advisor/pkg/app/utils.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana-app-sdk/resource" advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/services/contexthandler" ) func getCheck(obj resource.Object, checkMap map[string]checks.Check) (checks.Check, error) { @@ -170,6 +171,7 @@ func processCheckRetry(ctx context.Context, log logging.Logger, client resource. } // Delete the retry annotation to mark the check as processed annotations := checks.DeleteAnnotations(ctx, obj, []string{checks.RetryAnnotation}) + return checks.SetAnnotations(ctx, client, obj, annotations) } @@ -197,7 +199,10 @@ func runStepsInParallel(ctx context.Context, log logging.Logger, spec *advisorv0 } }() logger := log.With("step", step.ID()) - stepErr, err = step.Run(ctx, logger, spec, item) + // Create a copy of the context with a cloned HTTP request to prevent + // concurrent modifications to the same header map + safeCtx := contexthandler.CopyWithReqContext(ctx) + stepErr, err = step.Run(safeCtx, logger, spec, item) }() mu.Lock() defer mu.Unlock() diff --git a/apps/advisor/pkg/app/utils_test.go b/apps/advisor/pkg/app/utils_test.go index df4b016db93..663d7cfdcf6 100644 --- a/apps/advisor/pkg/app/utils_test.go +++ b/apps/advisor/pkg/app/utils_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "testing" "github.com/grafana/grafana-app-sdk/logging" @@ -11,7 +12,13 @@ import ( advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetCheck(t *testing.T) { @@ -289,13 +296,75 @@ func TestProcessCheckRetry_Success(t *testing.T) { assert.Empty(t, obj.Status.Report.Failures) } +func TestRunStepsInParallel_ConcurrentHeaderAccess(t *testing.T) { + // Create an HTTP request with headers to simulate the real scenario + req, err := http.NewRequest("GET", "/test", nil) + require.NoError(t, err) + req.Header.Set("X-Test-Header", "test-value") + req.Header.Set("X-Panel-Id", "123") + req.Header.Set("Cookie", "session=abc123; user_pref=dark") + + // Create a context with ReqContext that includes the HTTP request + webCtx := &web.Context{ + Req: req, + } + reqCtx := &contextmodel.ReqContext{ + Context: webCtx, + SignedInUser: &user.SignedInUser{}, + } + + ctx := ctxkey.Set(context.Background(), reqCtx) + + // Create steps that modify headers concurrently (simulating CookiesMiddleware behavior) + steps := []checks.Step{ + &headerModifyingStep{headerName: "X-Test-Header", headerValue: "modified-1"}, + &headerModifyingStep{headerName: "Cookie", headerValue: "session=xyz456"}, + &headerModifyingStep{headerName: "X-Panel-Id", headerValue: "456"}, + } + + // Create multiple items to process + const numItems = 20 + items := make([]any, numItems) + for i := 0; i < numItems; i++ { + items[i] = fmt.Sprintf("item-%d", i) + } + + // Track panics that might occur during execution + var panicCount int32 + originalPanicHandler := func() { + if r := recover(); r != nil { + panicCount++ + t.Errorf("Unexpected panic during concurrent header access: %v", r) + } + } + + // This test should not panic with our fix + t.Run("should not panic with concurrent header modifications", func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + originalPanicHandler() + } + }() + + failures, err := runStepsInParallel(ctx, logging.DefaultLogger, nil, steps, items) + + // Verify no error occurred + assert.NoError(t, err) + // Should have no failures since our mock step doesn't report failures + assert.Empty(t, failures) + // Verify no panics occurred + assert.Equal(t, int32(0), panicCount) + }) +} + type mockClient struct { resource.Client values []any } func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions, obj resource.Object) error { - m.values = append(m.values, req.Operations[0].Value) + value := req.Operations[0].Value + m.values = append(m.values, value) return nil } @@ -376,3 +445,49 @@ func (m *mockStep) Resolution() string { func (m *mockStep) ID() string { return "mock" } + +// headerModifyingStep is a mock step that modifies HTTP headers to simulate +// the behavior of CookiesMiddleware and other middleware that caused the original panic +type headerModifyingStep struct { + headerName string + headerValue string +} + +func (h *headerModifyingStep) Run(ctx context.Context, log logging.Logger, obj *advisorv0alpha1.CheckSpec, item any) ([]advisorv0alpha1.CheckReportFailure, error) { + // Get the request context and modify headers (this used to cause panics) + reqCtx := contexthandler.FromContext(ctx) + if reqCtx != nil && reqCtx.Req != nil { + // This is the type of header modification that was causing the concurrent map access panic + reqCtx.Req.Header.Set(h.headerName, h.headerValue) + reqCtx.Req.Header.Add("X-Processed-By", h.ID()) + + // Also test header deletion like ClearCookieHeader does + if h.headerName == "Cookie" { + reqCtx.Req.Header.Del("Cookie") + reqCtx.Req.Header.Set("Cookie", h.headerValue) + } + + // Test reading headers as well + _ = reqCtx.Req.Header.Get("X-Test-Header") + _ = reqCtx.Req.Header.Get("X-Panel-Id") + } + + // No failures to report + return nil, nil +} + +func (h *headerModifyingStep) Title() string { + return "Header Modifying Step" +} + +func (h *headerModifyingStep) Description() string { + return "A mock step that modifies HTTP headers to test concurrent access" +} + +func (h *headerModifyingStep) Resolution() string { + return "This is a test step" +} + +func (h *headerModifyingStep) ID() string { + return fmt.Sprintf("header-modifier-%s", h.headerName) +} diff --git a/apps/alerting/notifications/Makefile b/apps/alerting/notifications/Makefile index d745632abf9..817a6d508ff 100644 --- a/apps/alerting/notifications/Makefile +++ b/apps/alerting/notifications/Makefile @@ -1,22 +1,4 @@ -APP_SDK_VERSION := v0.39.0 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy +include ../../sdk.mk .PHONY: generate generate: do-generate ## Run Grafana App SDK code generation @@ -24,4 +6,4 @@ generate: do-generate ## Run Grafana App SDK code generation .PHONY: do-generate do-generate: install-app-sdk update-app-sdk ## --defencoding=none and noschemasinmanifest are needed to avoid infinite loop while generating recursive models (see routingtree.cue) - @$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=none --postprocess --noschemasinmanifest + @$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=none --postprocess --noschemasinmanifest --useoldmanifestkinds diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index f9ea5babd51..b885b4d1eb4 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -1,12 +1,12 @@ module github.com/grafana/grafana/apps/alerting/notifications -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 - github.com/grafana/grafana-app-sdk/logging v0.39.1 - k8s.io/apimachinery v0.33.2 - k8s.io/apiserver v0.33.2 + github.com/grafana/grafana-app-sdk v0.40.0 + github.com/grafana/grafana-app-sdk/logging v0.39.3 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -19,12 +19,12 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -38,14 +38,14 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -58,7 +58,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect @@ -70,20 +70,21 @@ require ( go.etcd.io/etcd/client/v3 v3.5.21 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect @@ -97,15 +98,15 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.2 // indirect + k8s.io/api v0.33.3 // indirect k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/client-go v0.33.2 // indirect - k8s.io/component-base v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index c906b01a1af..c5caf4fb4e0 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -25,8 +25,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -42,8 +42,8 @@ github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaE github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -84,18 +84,18 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -121,8 +121,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -159,8 +159,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= @@ -215,26 +215,26 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -245,6 +245,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -276,8 +280,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -352,32 +356,33 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= -k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= -k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/alerting/notifications/kinds/receiver.cue b/apps/alerting/notifications/kinds/receiver.cue index 9342f60298b..bf0dba84987 100644 --- a/apps/alerting/notifications/kinds/receiver.cue +++ b/apps/alerting/notifications/kinds/receiver.cue @@ -11,12 +11,12 @@ receiver: { } pluralName: "Receivers" current: "v0alpha1" - codegen: { - ts: {enabled: false} - go: {enabled: true} - } versions: { "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } schema: { spec: v0alpha1.ReceiverSpec } diff --git a/apps/alerting/notifications/kinds/routingtree.cue b/apps/alerting/notifications/kinds/routingtree.cue index cff9f8cd243..8fd527ff6f0 100644 --- a/apps/alerting/notifications/kinds/routingtree.cue +++ b/apps/alerting/notifications/kinds/routingtree.cue @@ -11,12 +11,12 @@ routeTree: { } pluralName: "RoutingTrees" current: "v0alpha1" - codegen: { - ts: {enabled: false} - go: {enabled: true} - } versions: { "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } schema: { spec: v0alpha1.RouteTreeSpec } diff --git a/apps/alerting/notifications/kinds/template.cue b/apps/alerting/notifications/kinds/template.cue index 0914b643c76..9eae7fadf1d 100644 --- a/apps/alerting/notifications/kinds/template.cue +++ b/apps/alerting/notifications/kinds/template.cue @@ -11,12 +11,12 @@ templateGroup: { } pluralName: "TemplateGroups" current: "v0alpha1" - codegen: { - ts: {enabled: false} - go: {enabled: true} - } versions: { "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } schema: { spec: v0alpha1.TemplateGroupSpec } diff --git a/apps/alerting/notifications/kinds/timeInterval.cue b/apps/alerting/notifications/kinds/timeInterval.cue index 2ba052895bc..4b5004ff0e5 100644 --- a/apps/alerting/notifications/kinds/timeInterval.cue +++ b/apps/alerting/notifications/kinds/timeInterval.cue @@ -11,12 +11,12 @@ timeInterval: { } pluralName: "TimeIntervals" current: "v0alpha1" - codegen: { - ts: {enabled: false} - go: {enabled: true} - } versions: { "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } schema: { spec: v0alpha1.TimeIntervalSpec } diff --git a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/receiver_schema_gen.go b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/receiver_schema_gen.go index 314d36b602a..acda5c7d1c9 100644 --- a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/receiver_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/receiver_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaReceiver = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &Receiver{}, &ReceiverList{}, resource.WithKind("Receiver"), - resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ + resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ FieldSelector: "spec.title", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*Receiver) diff --git a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/templategroup_schema_gen.go b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/templategroup_schema_gen.go index 509bfc4aeca..339d0bd6d04 100644 --- a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/templategroup_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/templategroup_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaTemplateGroup = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TemplateGroup{}, &TemplateGroupList{}, resource.WithKind("TemplateGroup"), - resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ + resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ FieldSelector: "spec.title", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*TemplateGroup) diff --git a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/timeinterval_schema_gen.go b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/timeinterval_schema_gen.go index 8c5459f0059..a6c809e0d3b 100644 --- a/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/timeinterval_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alerting/v0alpha1/timeinterval_schema_gen.go @@ -13,7 +13,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( schemaTimeInterval = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TimeInterval{}, &TimeIntervalList{}, resource.WithKind("TimeInterval"), - resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ + resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{ FieldSelector: "spec.name", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*TimeInterval) diff --git a/apps/alerting/notifications/pkg/apis/alerting_manifest.go b/apps/alerting/notifications/pkg/apis/alerting_manifest.go index 9b1e424d463..40f05004004 100644 --- a/apps/alerting/notifications/pkg/apis/alerting_manifest.go +++ b/apps/alerting/notifications/pkg/apis/alerting_manifest.go @@ -7,6 +7,7 @@ package apis import ( "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -14,56 +15,48 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1" ) +var () + var appManifestData = app.ManifestData{ AppName: "alerting", Group: "notifications.alerting.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Receiver", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", + Kind: "Receiver", + Plural: "Receivers", + Scope: "Namespaced", + Conversion: false, SelectableFields: []string{ "spec.title", }, }, - }, - }, - { - Kind: "RoutingTree", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "RoutingTree", + Plural: "RoutingTrees", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "TemplateGroup", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "TemplateGroup", + Plural: "TemplateGroups", + Scope: "Namespaced", + Conversion: false, SelectableFields: []string{ "spec.title", }, }, - }, - }, - { - Kind: "TimeInterval", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "TimeInterval", + Plural: "TimeIntervals", + Scope: "Namespaced", + Conversion: false, SelectableFields: []string{ "spec.name", }, @@ -94,3 +87,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile index 950c809c0ec..c3905410aed 100644 --- a/apps/dashboard/Makefile +++ b/apps/dashboard/Makefile @@ -1,22 +1,4 @@ -APP_SDK_VERSION := v0.39.2 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy +include ../sdk.mk .PHONY: generate generate: do-generate post-generate-cleanup ## Run Grafana App SDK code generation @@ -30,7 +12,8 @@ do-generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generati --grouping=group \ --defencoding=none \ --genoperatorstate=false \ - --noschemasinmanifest + --noschemasinmanifest \ + --useoldmanifestkinds .PHONY: post-generate-cleanup post-generate-cleanup: ## Clean up the generated code diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 837887e08e1..dc97db7ddc0 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -1,14 +1,14 @@ module github.com/grafana/grafana/apps/dashboard -go 1.24.4 +go 1.24.5 require ( cuelang.org/go v0.11.1 - github.com/grafana/grafana-app-sdk v0.39.2 + github.com/grafana/grafana-app-sdk v0.40.0 github.com/grafana/grafana-plugin-sdk-go v0.278.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/stretchr/testify v1.10.0 - k8s.io/apimachinery v0.33.2 + k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -24,12 +24,12 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v1.7.2 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -44,13 +44,15 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect @@ -61,7 +63,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magefile/mage v1.15.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -80,7 +82,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -95,21 +97,23 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect @@ -123,11 +127,11 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index b73b3aab637..7e6bab5fbb7 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -54,8 +54,8 @@ github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7M github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -94,10 +94,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= @@ -110,10 +110,15 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -149,8 +154,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -208,8 +213,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= @@ -270,34 +275,38 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -319,8 +328,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -379,24 +388,25 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/dashboard/kinds/dashboard.cue b/apps/dashboard/kinds/dashboard.cue index d48765539c8..7c68295d394 100644 --- a/apps/dashboard/kinds/dashboard.cue +++ b/apps/dashboard/kinds/dashboard.cue @@ -33,7 +33,6 @@ dashboard: { kind: "Dashboard" pluralName: "Dashboards" current: "v1beta1" - codegen: { ts: { enabled: true diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 59f0032ab5f..2a3c0408c29 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -7,6 +7,7 @@ package apis import ( "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -17,29 +18,60 @@ import ( v2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" ) +var () + var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Dashboard", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", + Kind: "Dashboard", + Plural: "Dashboards", + Scope: "Namespaced", + Conversion: false, }, + }, + }, + { + Name: "v1beta1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v1beta1", + Kind: "Dashboard", + Plural: "Dashboards", + Scope: "Namespaced", + Conversion: false, }, + }, + }, + { + Name: "v2alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v2alpha1", + Kind: "Dashboard", + Plural: "Dashboards", + Scope: "Namespaced", + Conversion: false, }, + }, + }, + { + Name: "v2alpha2", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v2alpha2", + Kind: "Dashboard", + Plural: "Dashboards", + Scope: "Namespaced", + Conversion: false, }, }, }, @@ -67,3 +99,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/folder/Makefile b/apps/folder/Makefile index 7a30038bc89..a6abc7a5337 100644 --- a/apps/folder/Makefile +++ b/apps/folder/Makefile @@ -1,22 +1,4 @@ -APP_SDK_VERSION := v0.39.0 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy +include ../sdk.mk .PHONY: generate generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation @@ -26,4 +8,5 @@ generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation --grouping=group \ --defencoding=none \ --genoperatorstate=false \ - --noschemasinmanifest + --noschemasinmanifest \ + --useoldmanifestkinds diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 88db83aa995..b378d607620 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -1,11 +1,11 @@ module github.com/grafana/grafana/apps/folder -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 + github.com/grafana/grafana-app-sdk v0.40.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e - k8s.io/apimachinery v0.33.2 + k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -13,20 +13,22 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -37,13 +39,14 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect @@ -53,11 +56,11 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 8f346fcbfcd..b7f94082f13 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -6,14 +6,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -32,12 +32,17 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -52,8 +57,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -78,8 +83,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -98,10 +103,14 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -150,24 +159,25 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/folder/kinds/folder.cue b/apps/folder/kinds/folder.cue index d0da0db7082..44423768205 100644 --- a/apps/folder/kinds/folder.cue +++ b/apps/folder/kinds/folder.cue @@ -4,17 +4,16 @@ folder: { kind: "Folder" pluralName: "Folders" current: "v1beta1" - codegen: { - ts: { - enabled: false // Not sure if it should be enabled or not, currently it is. - } - go: { - enabled: true - } - } - versions: { "v1beta1": { + codegen: { + ts: { + enabled: false // Not sure if it should be enabled or not, currently it is. + } + go: { + enabled: true + } + } schema: { spec: { title: string diff --git a/apps/folder/pkg/apis/folder_manifest.go b/apps/folder/pkg/apis/folder_manifest.go index 8491ddc6083..8628efe6537 100644 --- a/apps/folder/pkg/apis/folder_manifest.go +++ b/apps/folder/pkg/apis/folder_manifest.go @@ -7,6 +7,7 @@ package apis import ( "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -14,17 +15,21 @@ import ( v1beta1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) +var () + var appManifestData = app.ManifestData{ AppName: "folder", Group: "folder.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Folder", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v1beta1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v1beta1", + Kind: "Folder", + Plural: "Folders", + Scope: "Namespaced", + Conversion: false, }, }, }, @@ -49,3 +54,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/iam/Makefile b/apps/iam/Makefile index 7188b890ad4..6839689c867 100644 --- a/apps/iam/Makefile +++ b/apps/iam/Makefile @@ -1,22 +1,4 @@ -APP_SDK_VERSION := v0.39.0 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy +include ../sdk.mk .PHONY: generate generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation @@ -26,4 +8,4 @@ generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation --grouping=group \ --defencoding=none \ --noschemasinmanifest \ - --postprocess \ No newline at end of file + --postprocess \ \ No newline at end of file diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 5c4086d8596..43260a0bf87 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -1,11 +1,11 @@ module github.com/grafana/grafana/apps/iam -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 + github.com/grafana/grafana-app-sdk v0.40.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e - k8s.io/apimachinery v0.33.2 + k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) @@ -13,20 +13,22 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -37,13 +39,14 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect @@ -53,11 +56,11 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 8f346fcbfcd..b7f94082f13 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -6,14 +6,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -32,12 +32,17 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -52,8 +57,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -78,8 +83,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -98,10 +103,14 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -150,24 +159,25 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/iam/kinds/manifest.cue b/apps/iam/kinds/manifest.cue index 032261e9030..60a6d1f8faf 100644 --- a/apps/iam/kinds/manifest.cue +++ b/apps/iam/kinds/manifest.cue @@ -1,18 +1,24 @@ package kinds manifest: { - appName: "iam" + appName: "iam" groupOverride: "iam.grafana.app" - kinds: [ - globalrole, - globalrolebinding, - corerole, - role, - rolebinding, - resourcepermission, - user, - team, - teambinding, - serviceaccount, - ] + versions: { + "v0alpha1": v0alpha1 + } } + +v0alpha1: { + kinds: [ + globalrolev0alpha1, + globalrolebindingv0alpha1, + corerolev0alpha1, + rolev0alpha1, + rolebindingv0alpha1, + resourcepermissionv0alpha1, + userv0alpha1, + teamv0alpha1, + teambindingv0alpha1, + serviceaccountv0alpha1, + ] +} \ No newline at end of file diff --git a/apps/iam/kinds/resourcepermission.cue b/apps/iam/kinds/resourcepermission.cue index 46e27b160fe..42fcddfe6fc 100644 --- a/apps/iam/kinds/resourcepermission.cue +++ b/apps/iam/kinds/resourcepermission.cue @@ -4,21 +4,17 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -resourcepermission: { +resourcepermissionKind: { kind: "ResourcePermission" pluralName: "ResourcePermissions" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.ResourcePermission - } - } +resourcepermissionv0alpha1: resourcepermissionKind & { + schema: { + spec: v0alpha1.ResourcePermission } } diff --git a/apps/iam/kinds/role.cue b/apps/iam/kinds/role.cue index 1422a57d246..1911fe83616 100644 --- a/apps/iam/kinds/role.cue +++ b/apps/iam/kinds/role.cue @@ -4,59 +4,47 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -role: { +roleKind: { kind: "Role" pluralName: "Roles" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } - - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.RoleSpec - } - } - } } -corerole: { +coreroleKind: { kind: "CoreRole" pluralName: "CoreRoles" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } - - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.RoleSpec - } - } - } } -globalrole: { +globalroleKind: { kind: "GlobalRole" pluralName: "GlobalRoles" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.RoleSpec - } - } +rolev0alpha1: roleKind & { + schema: { + spec: v0alpha1.RoleSpec + } +} + +corerolev0alpha1: coreroleKind & { + schema: { + spec: v0alpha1.RoleSpec + } +} + +globalrolev0alpha1: globalroleKind & { + schema: { + spec: v0alpha1.RoleSpec } } \ No newline at end of file diff --git a/apps/iam/kinds/rolebinding.cue b/apps/iam/kinds/rolebinding.cue index caee8c22717..119b958970c 100644 --- a/apps/iam/kinds/rolebinding.cue +++ b/apps/iam/kinds/rolebinding.cue @@ -4,40 +4,32 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -rolebinding: { +rolebindingKind: { kind: "RoleBinding" pluralName: "RoleBindings" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } - - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.RoleBindingSpec - } - } - } } -globalrolebinding: { +globalrolebindingKind: { kind: "GlobalRoleBinding" pluralName: "GlobalRoleBindings" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.GlobalRoleBindingSpec - } - } +rolebindingv0alpha1: rolebindingKind & { + schema: { + spec: v0alpha1.RoleBindingSpec + } +} + +globalrolebindingv0alpha1: globalrolebindingKind & { + schema: { + spec: v0alpha1.GlobalRoleBindingSpec } } diff --git a/apps/iam/kinds/serviceaccount.cue b/apps/iam/kinds/serviceaccount.cue index 22fb1e9e9cf..72b5c10025b 100644 --- a/apps/iam/kinds/serviceaccount.cue +++ b/apps/iam/kinds/serviceaccount.cue @@ -4,21 +4,17 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -serviceaccount: { +serviceaccountKind: { kind: "ServiceAccount" pluralName: "ServiceAccounts" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } - - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.ServiceAccountSpec - } - } - } } + +serviceaccountv0alpha1: serviceaccountKind & { + schema: { + spec: v0alpha1.ServiceAccountSpec + } +} \ No newline at end of file diff --git a/apps/iam/kinds/team.cue b/apps/iam/kinds/team.cue index 284d9e601ea..9acf9d60c01 100644 --- a/apps/iam/kinds/team.cue +++ b/apps/iam/kinds/team.cue @@ -4,21 +4,18 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -team: { +teamKind: { kind: "Team" pluralName: "Teams" current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.TeamSpec - } - } +teamv0alpha1: teamKind & { + schema: { + spec: v0alpha1.TeamSpec } } diff --git a/apps/iam/kinds/teambinding.cue b/apps/iam/kinds/teambinding.cue index 3f0805b0307..60870b58853 100644 --- a/apps/iam/kinds/teambinding.cue +++ b/apps/iam/kinds/teambinding.cue @@ -4,21 +4,17 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -teambinding: { +teambindingKind: { kind: "TeamBinding" pluralName: "TeamBindings" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - schema: { - spec: v0alpha1.TeamBindingSpec - } - } +teambindingv0alpha1: teambindingKind & { + schema: { + spec: v0alpha1.TeamBindingSpec } } diff --git a/apps/iam/kinds/user.cue b/apps/iam/kinds/user.cue index c8fc73c184c..480391dfa2e 100644 --- a/apps/iam/kinds/user.cue +++ b/apps/iam/kinds/user.cue @@ -4,28 +4,17 @@ import ( "github.com/grafana/grafana/apps/iam/kinds/v0alpha1" ) -user: { +userKind: { kind: "User" pluralName: "Users" - current: "v0alpha1" - codegen: { ts: { enabled: false } go: { enabled: true } } +} - versions: { - "v0alpha1": { - validation: { - operations: [ - "CREATE", - "UPDATE", - ] - } - - schema: { - spec: v0alpha1.UserSpec - } - } +userv0alpha1: userKind & { + schema: { + spec: v0alpha1.UserSpec } } diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index 2ae327f76e6..c534546ff64 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -7,6 +7,7 @@ package apis import ( "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -14,126 +15,82 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" ) -var () - var appManifestData = app.ManifestData{ AppName: "iam", Group: "iam.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "GlobalRole", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", + Kind: "GlobalRole", + Plural: "GlobalRoles", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "GlobalRoleBinding", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "GlobalRoleBinding", + Plural: "GlobalRoleBindings", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "CoreRole", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "CoreRole", + Plural: "CoreRoles", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "Role", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "Role", + Plural: "Roles", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "RoleBinding", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "RoleBinding", + Plural: "RoleBindings", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "ResourcePermission", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "ResourcePermission", + Plural: "ResourcePermissions", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "User", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", - Admission: &app.AdmissionCapabilities{ - Validation: &app.ValidationCapability{ - Operations: []app.AdmissionOperation{ - app.AdmissionOperationCreate, - app.AdmissionOperationUpdate, - }, - }, - }, + Kind: "User", + Plural: "Users", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "Team", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "Team", + Plural: "Teams", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "TeamBinding", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "TeamBinding", + Plural: "TeamBindings", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "ServiceAccount", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", + Kind: "ServiceAccount", + Plural: "ServiceAccounts", + Scope: "Namespaced", + Conversion: false, }, }, }, @@ -167,3 +124,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/investigations/Makefile b/apps/investigations/Makefile index 7a5f1c99365..aaac06377ca 100644 --- a/apps/investigations/Makefile +++ b/apps/investigations/Makefile @@ -1,3 +1,5 @@ +include ../sdk.mk + .PHONY: generate -generate: - @grafana-app-sdk generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none +generate: install-app-sdk update-app-sdk + @$(APP_SDK_BIN) generate -g ./pkg/apis --grouping=group --postprocess --defencoding=none --useoldmanifestkinds diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index f37ff9db9e4..ec8c3d01dae 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -1,94 +1,215 @@ module github.com/grafana/grafana/apps/investigations -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.0 - k8s.io/apimachinery v0.33.1 + github.com/grafana/grafana v0.0.0-00010101000000-000000000000 + github.com/grafana/grafana-app-sdk v0.40.0 + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec + github.com/stretchr/testify v1.10.0 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) +// transitive dependencies that need replaced +// TODO: stop depending on grafana core +replace github.com/grafana/grafana => ../.. + +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 + require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect + github.com/apache/arrow-go/v18 v18.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect + github.com/aws/smithy-go v1.22.4 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bluele/gcache v0.0.2 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v1.0.0 // indirect + github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/elazarl/goproxy v1.7.2 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-migrate/migrate/v4 v4.7.0 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // indirect - github.com/grafana/grafana-app-sdk/logging v0.38.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b // indirect + github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/dataplane/sdata v0.0.9 // indirect + github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect + github.com/grafana/grafana-aws-sdk v1.0.4 // indirect + github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // indirect + github.com/grafana/grafana-plugin-sdk-go v0.278.0 // indirect + github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/sqlds/v4 v4.2.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/jaegertracing/jaeger-idl v0.5.0 // indirect + github.com/jmespath-community/go-jmespath v1.1.1 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/magefile/mage v1.15.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mithrandie/csvq v1.18.1 // indirect + github.com/mithrandie/csvq-driver v1.7.0 // indirect + github.com/mithrandie/go-file/v2 v2.1.0 // indirect + github.com/mithrandie/go-text v1.6.0 // indirect + github.com/mithrandie/ternary v1.1.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/onsi/ginkgo/v2 v2.22.2 // indirect - github.com/onsi/gomega v1.36.2 // indirect - github.com/openfga/openfga v1.8.13 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/open-feature/go-sdk v1.14.1 // indirect + github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 // indirect + github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/alertmanager v0.28.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tjhop/slog-gokit v0.1.3 // indirect + github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect + github.com/unknwon/com v1.0.1 // indirect + github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect + github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect - golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.34.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.1 // indirect - k8s.io/apiextensions-apiserver v0.33.1 // indirect - k8s.io/client-go v0.33.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect + xorm.io/builder v0.3.6 // indirect ) diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index fbf07786493..903c4ae9e07 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -1,279 +1,993 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= +cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/apache/arrow-go/v18 v18.3.0 h1:Xq4A6dZj9Nu33sqZibzn012LNnewkTUlfKVUFD/RX/I= +github.com/apache/arrow-go/v18 v18.3.0/go.mod h1:eEM1DnUTHhgGAjf/ChvOAQbUQ+EPohtDrArffvUjPg8= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/at-wat/mqtt-go v0.19.4 h1:R2cbCU7O5PHQ38unbe1Y51ncG3KsFEJV6QeipDoqdLQ= +github.com/at-wat/mqtt-go v0.19.4/go.mod h1:AsiWc9kqVOhqq7LzUeWT/AkKUBfx3Sw5cEe8lc06fqA= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= +github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI= +github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxWhbByHgQSCUQvY= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAttAqWaudUAsM9iHASi/4eFBK+qn4qeaNto7g8bK4= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= -github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= +github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= +github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= +github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb h1:oTl2j6/4miQUYmXANp2pBuYCWA5f8NVYFfCWpczpFso= -github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= -github.com/grafana/grafana-app-sdk v0.35.1 h1:zEXubzsQrxGBOzXJJMBwhEClC/tvPi0sfK7NGmlX3RI= -github.com/grafana/grafana-app-sdk v0.35.1/go.mod h1:Zx5MkVppYK+ElSDUAR6+fjzOVo6I/cIgk+ty+LmNOxI= -github.com/grafana/grafana-app-sdk v0.39.0/go.mod h1:xRyBQOttgWTc3tGe9pI0upnpEPVhzALf7Mh/61O4zyY= -github.com/grafana/grafana-app-sdk/logging v0.35.1 h1:taVpl+RoixTYl0JBJGhH+fPVmwA9wvdwdzJTZsv9buM= -github.com/grafana/grafana-app-sdk/logging v0.35.1/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= -github.com/grafana/grafana-app-sdk/logging v0.38.2/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b h1:mfUAq/N+mS82EcE35hDXWtfVY7UhTjzZxzssvFt9tvQ= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= +github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= +github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= +github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= +github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec h1:cg1GbDVZ7goqDrqoMzqeN4AeAcD271MGYjOvdVTDwfw= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec/go.mod h1:3ZgUe0E3rIhI026xF4DKFptOst/jpDHJ/Sn+bRODzI4= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:6OKkPWDB8PetDXqMVMOWL35iTCEUdpATwwpuew0k8+o= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 h1:oJnbhG6ZNy10AjsgNeAtAKeGHogIGOMfAsBH6fYYa5M= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/sqlds/v4 v4.2.3 h1:9ibD1c5O5u9fifEkBSig+jAc41TUEz+M+bWQqDsofP4= +github.com/grafana/sqlds/v4 v4.2.3/go.mod h1:bv+XHabfUF4xkgg4y+nYFCK8rpMHZsMaQk56qNaJcAM= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= +github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= +github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= +github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mithrandie/csvq v1.18.1 h1:f7NB2scbb7xx2ffPduJ2VtZ85RpWXfvanYskAkGlCBU= +github.com/mithrandie/csvq v1.18.1/go.mod h1:MRJj7AtcXfk7jhNGxLuJGP3LORmh4lpiPWxQ7VyCRn8= +github.com/mithrandie/csvq-driver v1.7.0 h1:ejiavXNWwTPMyr3fJFnhcqd1L1cYudA0foQy9cZrqhw= +github.com/mithrandie/csvq-driver v1.7.0/go.mod h1:HcN3xL9UCJnBYA/AIQOOB/KlyfXAiYr5yxDmiwrGk5o= +github.com/mithrandie/go-file/v2 v2.1.0 h1:XA5Tl+73GXMDvgwSE3Sg0uC5FkLr3hnXs8SpUas0hyg= +github.com/mithrandie/go-file/v2 v2.1.0/go.mod h1:9YtTF3Xo59GqC1Pxw6KyGVcM/qubAMlxVsqI/u9r++c= +github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyWO6s= +github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to= +github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4= +github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/openfga/openfga v1.8.12 h1:xEirA6tFwaJfjBDtbHWCK0/Tw+B8XleRyhg9dcEpzHo= -github.com/openfga/openfga v1.8.12/go.mod h1:fIZyekdNB+tWQ6zIiglZonAc5ErZiDGMeHue/BzRYRM= -github.com/openfga/openfga v1.8.13 h1:ROURkotKhbmtyBX3188+cNElN8AOZmTl0CMkxUqwawo= -github.com/openfga/openfga v1.8.13/go.mod h1:h1VGcVW81eY1YyDtFx5+gxxAIEhIiOGR9SRGgs/X/k8= +github.com/open-feature/go-sdk v1.14.1 h1:jcxjCIG5Up3XkgYwWN5Y/WWfc6XobOhqrIwjyDBsoQo= +github.com/open-feature/go-sdk v1.14.1/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 h1:6jpO63NCEZv4xunJj+aNlDuFVuRkVBPMcIuxvFPYRWQ= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3/go.mod h1:dPUHjAIFzg+ci/wt6XxlNiiMkOh5Yw4SGyeRY0AFT0g= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 h1:ZdqlGnNwhWf3luhBQlIpbglvcCzjkcuEgOEhYhr5Emc= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5/go.mod h1:jrD4UG3ZCzuwImKHlyuIN2iWeYjlOX5+zJ/sX45efuE= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= +github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= +github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= +github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= +go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= +gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= -k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= +xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= +xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go b/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go index 288c15885ce..bd538eb9c30 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/constants.go @@ -3,16 +3,16 @@ package v0alpha1 import "k8s.io/apimachinery/pkg/runtime/schema" const ( - // Group is the API group used by all kinds in this package - Group = "investigations.grafana.app" - // Version is the API version used by all kinds in this package - Version = "v0alpha1" + // APIGroup is the API group used by all kinds in this package + APIGroup = "investigations.grafana.app" + // APIVersion is the API version used by all kinds in this package + APIVersion = "v0alpha1" ) var ( // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package GroupVersion = schema.GroupVersion{ - Group: Group, - Version: Version, + Group: APIGroup, + Version: APIVersion, } ) diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go index e0d2756f194..ffd588fec5e 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_metadata_gen.go @@ -24,5 +24,8 @@ type InvestigationMetadata struct { // NewInvestigationMetadata creates a new InvestigationMetadata object. func NewInvestigationMetadata() *InvestigationMetadata { - return &InvestigationMetadata{} + return &InvestigationMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } } diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go index 9ff1a1d4275..d56124603a2 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_spec_gen.go @@ -40,6 +40,7 @@ type InvestigationCollectable struct { // NewInvestigationCollectable creates a new InvestigationCollectable object. func NewInvestigationCollectable() *InvestigationCollectable { return &InvestigationCollectable{ + Queries: []string{}, TimeRange: *NewInvestigationTimeRange(), Datasource: *NewInvestigationDatasourceRef(), } @@ -101,6 +102,7 @@ type InvestigationSpec struct { func NewInvestigationSpec() *InvestigationSpec { return &InvestigationSpec{ CreatedByProfile: *NewInvestigationPerson(), + Collectables: []InvestigationCollectable{}, ViewMode: *NewInvestigationViewMode(), } } diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go index 67e97598a93..e7bef02a137 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_metadata_gen.go @@ -24,5 +24,8 @@ type InvestigationIndexMetadata struct { // NewInvestigationIndexMetadata creates a new InvestigationIndexMetadata object. func NewInvestigationIndexMetadata() *InvestigationIndexMetadata { - return &InvestigationIndexMetadata{} + return &InvestigationIndexMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } } diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go index 4180a63a326..44be927c850 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_spec_gen.go @@ -35,8 +35,9 @@ type InvestigationIndexInvestigationSummary struct { // NewInvestigationIndexInvestigationSummary creates a new InvestigationIndexInvestigationSummary object. func NewInvestigationIndexInvestigationSummary() *InvestigationIndexInvestigationSummary { return &InvestigationIndexInvestigationSummary{ - CreatedByProfile: *NewInvestigationIndexPerson(), - ViewMode: *NewInvestigationIndexViewMode(), + CreatedByProfile: *NewInvestigationIndexPerson(), + ViewMode: *NewInvestigationIndexViewMode(), + CollectableSummaries: []InvestigationIndexCollectableSummary{}, } } @@ -79,7 +80,8 @@ type InvestigationIndexSpec struct { // NewInvestigationIndexSpec creates a new InvestigationIndexSpec object. func NewInvestigationIndexSpec() *InvestigationIndexSpec { return &InvestigationIndexSpec{ - Owner: *NewInvestigationIndexPerson(), + Owner: *NewInvestigationIndexPerson(), + InvestigationSummaries: []InvestigationIndexInvestigationSummary{}, } } diff --git a/apps/investigations/pkg/apis/investigations_manifest.go b/apps/investigations/pkg/apis/investigations_manifest.go index be270ae598f..908b542ffd0 100644 --- a/apps/investigations/pkg/apis/investigations_manifest.go +++ b/apps/investigations/pkg/apis/investigations_manifest.go @@ -7,8 +7,13 @@ package apis import ( "encoding/json" + "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + + v0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1" ) var ( @@ -23,39 +28,31 @@ var ( var appManifestData = app.ManifestData{ AppName: "investigations", Group: "investigations.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Investigation", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", - Schema: &versionSchemaInvestigationv0alpha1, + Kind: "Investigation", + Plural: "Investigations", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaInvestigationv0alpha1, }, - }, - }, - { - Kind: "InvestigationIndex", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v0alpha1", - Schema: &versionSchemaInvestigationIndexv0alpha1, + Kind: "InvestigationIndex", + Plural: "InvestigationIndexes", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaInvestigationIndexv0alpha1, }, }, }, }, } -func jsonToMap(j string) map[string]any { - m := make(map[string]any) - json.Unmarshal([]byte(j), &j) - return m -} - func LocalManifest() app.Manifest { return app.NewEmbeddedManifest(appManifestData) } @@ -63,3 +60,28 @@ func LocalManifest() app.Manifest { func RemoteManifest() app.Manifest { return app.NewAPIServerManifest("investigations") } + +var kindVersionToGoType = map[string]resource.Kind{ + "Investigation/v0alpha1": v0alpha1.InvestigationKind(), + "InvestigationIndex/v0alpha1": v0alpha1.InvestigationIndexKind(), +} + +// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. +// If there is no association for the provided Kind and Version, exists will return false. +func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) { + goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] + return goType, exists +} + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/playlist/Makefile b/apps/playlist/Makefile index 7188b890ad4..230bfd4149a 100644 --- a/apps/playlist/Makefile +++ b/apps/playlist/Makefile @@ -1,29 +1,9 @@ -APP_SDK_VERSION := v0.39.0 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk +include ../sdk.mk -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy - -.PHONY: generate -generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation +.PHONY: generate # Run Grafana App SDK code generation +generate: install-app-sdk update-app-sdk @$(APP_SDK_BIN) generate \ --source=./kinds/ \ --gogenpath=./pkg/apis \ --grouping=group \ - --defencoding=none \ - --noschemasinmanifest \ - --postprocess \ No newline at end of file + --defencoding=none \ No newline at end of file diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 47c762b5a24..1bb57fe534b 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -1,13 +1,12 @@ module github.com/grafana/grafana/apps/playlist -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 - k8s.io/apimachinery v0.33.2 - k8s.io/client-go v0.33.2 + github.com/grafana/grafana-app-sdk v0.40.0 + k8s.io/apimachinery v0.33.3 + k8s.io/client-go v0.33.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) require ( @@ -16,11 +15,11 @@ require ( github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -31,13 +30,13 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -49,25 +48,26 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect @@ -79,11 +79,12 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.2 // indirect + k8s.io/api v0.33.3 // indirect k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index 9cbf8457905..6558ce86a17 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -19,8 +19,8 @@ github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXE github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -47,12 +47,12 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -72,8 +72,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -102,8 +102,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= @@ -126,26 +126,30 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -162,8 +166,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -206,26 +210,27 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/playlist/kinds/manifest.cue b/apps/playlist/kinds/manifest.cue index 172bc419200..aec60b82f9e 100644 --- a/apps/playlist/kinds/manifest.cue +++ b/apps/playlist/kinds/manifest.cue @@ -3,7 +3,15 @@ package playlist manifest: { appName: "playlist" groupOverride: "playlist.grafana.app" - kinds: [ - playlist, - ] + versions: { + "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + playlistv0alpha1, + ] + } + } } diff --git a/apps/playlist/kinds/playlist.cue b/apps/playlist/kinds/playlist.cue index bfcc5db6aed..e6a4d39a0b6 100644 --- a/apps/playlist/kinds/playlist.cue +++ b/apps/playlist/kinds/playlist.cue @@ -1,46 +1,38 @@ package playlist -playlist: { - kind: "Playlist" - pluralName: "Playlists" - current: "v0alpha1" - versions: { - "v0alpha1": { - codegen: { - frontend: false - backend: true - } - validation: { - operations: [ - "CREATE", - "UPDATE", - ] - } - mutation: { - operations: [ - "CREATE", - "UPDATE", - ] - } - schema: { - #Item: { - // type of the item. - type: "dashboard_by_tag" | "dashboard_by_uid" | "dashboard_by_id" @cuetsy(kind="enum") - // Value depends on type and describes the playlist item. - // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - // is not portable as the numerical identifier is non-deterministic between different instances. - // Will be replaced by dashboard_by_uid in the future. (deprecated) - // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - // dashboards behind the tag will be added to the playlist. - // - dashboard_by_uid: The value is the dashboard UID - value: string - } - spec: { - title: string - interval: string - items: [...#Item] - } - } +playlistv0alpha1: { + kind: "Playlist" + plural: "playlists" + scope: "Namespaced" + validation: { + operations: [ + "CREATE", + "UPDATE", + ] + } + mutation: { + operations: [ + "CREATE", + "UPDATE", + ] + } + schema: { + #Item: { + // type of the item. + type: "dashboard_by_tag" | "dashboard_by_uid" | "dashboard_by_id" @cuetsy(kind="enum") + // Value depends on type and describes the playlist item. + // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + // is not portable as the numerical identifier is non-deterministic between different instances. + // Will be replaced by dashboard_by_uid in the future. (deprecated) + // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + // dashboards behind the tag will be added to the playlist. + // - dashboard_by_uid: The value is the dashboard UID + value: string + } + spec: { + title: string + interval: string + items: [...#Item] } } } diff --git a/apps/playlist/pkg/apis/playlist/v0alpha1/zz_openapi_gen.go b/apps/playlist/pkg/apis/playlist/v0alpha1/zz_openapi_gen.go deleted file mode 100644 index 8dba9b173b6..00000000000 --- a/apps/playlist/pkg/apis/playlist/v0alpha1/zz_openapi_gen.go +++ /dev/null @@ -1,284 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by grafana-app-sdk. DO NOT EDIT. - -package v0alpha1 - -import ( - common "k8s.io/kube-openapi/pkg/common" - spec "k8s.io/kube-openapi/pkg/validation/spec" -) - -func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.Playlist": schema_pkg_apis_playlist_v0alpha1_Playlist(ref), - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistItem": schema_pkg_apis_playlist_v0alpha1_PlaylistItem(ref), - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistList": schema_pkg_apis_playlist_v0alpha1_PlaylistList(ref), - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistSpec": schema_pkg_apis_playlist_v0alpha1_PlaylistSpec(ref), - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistStatus": schema_pkg_apis_playlist_v0alpha1_PlaylistStatus(ref), - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlayliststatusOperatorState": schema_pkg_apis_playlist_v0alpha1_PlayliststatusOperatorState(ref), - } -} - -func schema_pkg_apis_playlist_v0alpha1_Playlist(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec is the spec of the Playlist", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistSpec", "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_playlist_v0alpha1_PlaylistItem(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Description: "type of the item.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Description: "Value depends on type and describes the playlist item.\n - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This\n is not portable as the numerical identifier is non-deterministic between different instances.\n Will be replaced by dashboard_by_uid in the future. (deprecated)\n - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All\n dashboards behind the tag will be added to the playlist.\n - dashboard_by_uid: The value is the dashboard UID", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"type", "value"}, - }, - }, - } -} - -func schema_pkg_apis_playlist_v0alpha1_PlaylistList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.Playlist"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.Playlist", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_playlist_v0alpha1_PlaylistSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "interval": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistItem"), - }, - }, - }, - }, - }, - }, - Required: []string{"title", "interval", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlaylistItem"}, - } -} - -func schema_pkg_apis_playlist_v0alpha1_PlaylistStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlayliststatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1.PlayliststatusOperatorState"}, - } -} - -func schema_pkg_apis_playlist_v0alpha1_PlayliststatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} diff --git a/apps/playlist/pkg/apis/playlist_manifest.go b/apps/playlist/pkg/apis/playlist_manifest.go index 7973fdf4e6d..bc2d3ad6930 100644 --- a/apps/playlist/pkg/apis/playlist_manifest.go +++ b/apps/playlist/pkg/apis/playlist_manifest.go @@ -6,7 +6,9 @@ package apis import ( + "encoding/json" "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -14,17 +16,25 @@ import ( v0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" ) +var ( + rawSchemaPlaylistv0alpha1 = []byte(`{"spec":{"properties":{"interval":{"type":"string"},"items":{"items":{"properties":{"type":{"description":"type of the item.","enum":["dashboard_by_tag","dashboard_by_uid","dashboard_by_id"],"type":"string"},"value":{"description":"Value depends on type and describes the playlist item.\n - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This\n is not portable as the numerical identifier is non-deterministic between different instances.\n Will be replaced by dashboard_by_uid in the future. (deprecated)\n - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All\n dashboards behind the tag will be added to the playlist.\n - dashboard_by_uid: The value is the dashboard UID","type":"string"}},"required":["type","value"],"type":"object"},"type":"array"},"title":{"type":"string"}},"required":["title","interval","items"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPlaylistv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPlaylistv0alpha1, &versionSchemaPlaylistv0alpha1) +) + var appManifestData = app.ManifestData{ AppName: "playlist", Group: "playlist.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "Playlist", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v0alpha1", + Kind: "Playlist", + Plural: "Playlists", + Scope: "Namespaced", + Conversion: false, Admission: &app.AdmissionCapabilities{ Validation: &app.ValidationCapability{ Operations: []app.AdmissionOperation{ @@ -39,6 +49,7 @@ var appManifestData = app.ManifestData{ }, }, }, + Schema: &versionSchemaPlaylistv0alpha1, }, }, }, @@ -63,3 +74,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/playlist/pkg/app/app.go b/apps/playlist/pkg/app/app.go index 184372310f5..222c144ef8c 100644 --- a/apps/playlist/pkg/app/app.go +++ b/apps/playlist/pkg/app/app.go @@ -12,9 +12,8 @@ import ( "k8s.io/client-go/rest" "k8s.io/klog/v2" - "github.com/grafana/grafana/apps/playlist/pkg/reconcilers" - playlistv0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" + "github.com/grafana/grafana/apps/playlist/pkg/reconcilers" ) type PlaylistConfig struct { diff --git a/apps/sdk.mk b/apps/sdk.mk new file mode 100644 index 00000000000..7a0f51237cf --- /dev/null +++ b/apps/sdk.mk @@ -0,0 +1,23 @@ +APP_SDK_VERSION = v0.40.0 +APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) +APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk + + +.PHONY: install-app-sdk +install-app-sdk: echodir $(APP_SDK_BIN) ## Install the Grafana App SDK + +.PHONY: echodir + +$(APP_SDK_BIN): + @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" + @mkdir -p $(APP_SDK_DIR) + # The only way to install specific versions of binaries using `go install` + # is by setting GOBIN to the directory you want to install the binary to. + GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) + @touch $@ + +.PHONY: update-app-sdk +update-app-sdk: ## Update the Grafana App SDK dependency in go.mod + @pwd + go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) + go mod tidy || true # TODO: remove this diff --git a/apps/secret/Makefile b/apps/secret/Makefile index d829f94e596..4826c9b871f 100644 --- a/apps/secret/Makefile +++ b/apps/secret/Makefile @@ -1,22 +1,4 @@ -APP_SDK_VERSION := v0.39.2 -APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) -APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk - -.PHONY: install-app-sdk -install-app-sdk: $(APP_SDK_BIN) ## Install the Grafana App SDK - -$(APP_SDK_BIN): - @echo "Installing Grafana App SDK version $(APP_SDK_VERSION)" - @mkdir -p $(APP_SDK_DIR) - # The only way to install specific versions of binaries using `go install` - # is by setting GOBIN to the directory you want to install the binary to. - GOBIN=$(APP_SDK_DIR) go install github.com/grafana/grafana-app-sdk/cmd/grafana-app-sdk@$(APP_SDK_VERSION) - @touch $@ - -.PHONY: update-app-sdk -update-app-sdk: ## Update the Grafana App SDK dependency in go.mod - go get github.com/grafana/grafana-app-sdk@$(APP_SDK_VERSION) - go mod tidy +include ../sdk.mk .PHONY: generate generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation @@ -26,4 +8,5 @@ generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation --grouping=group \ --defencoding=none \ --noschemasinmanifest \ - --postprocess \ No newline at end of file + --postprocess \ + --useoldmanifestkinds \ No newline at end of file diff --git a/apps/secret/go.mod b/apps/secret/go.mod index 0c6f4df1e16..bd2ef50ab1b 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -1,37 +1,39 @@ module github.com/grafana/grafana/apps/secret -go 1.24.4 +go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.39.2 + github.com/grafana/grafana-app-sdk v0.40.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.33.2 + k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + k8s.io/utils v0.0.0-20241210054802-24370beab758 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.39.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.39.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -42,13 +44,14 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect @@ -57,10 +60,10 @@ require ( golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/apps/secret/go.sum b/apps/secret/go.sum index fb88ea01837..0e4f4908526 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -6,14 +6,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -36,12 +36,17 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf/go.mod h1:eAlOam2uWhrsEZlOoAr7XZ9hbBP7SyYGYn31/aQAPs8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -56,8 +61,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -82,8 +87,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -104,16 +109,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -166,24 +175,25 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/secret/kinds/keeper.cue b/apps/secret/kinds/keeper.cue index 117e3bbd8a3..65eab07afc0 100644 --- a/apps/secret/kinds/keeper.cue +++ b/apps/secret/kinds/keeper.cue @@ -7,7 +7,6 @@ keeper: { pluralName: "Keepers" current: "v1beta1" scope: "Namespaced" - current: "v1beta1" codegen: { ts: { enabled: false diff --git a/apps/secret/kinds/securevalue.cue b/apps/secret/kinds/securevalue.cue index 6bed093f46d..d37a88fce52 100644 --- a/apps/secret/kinds/securevalue.cue +++ b/apps/secret/kinds/securevalue.cue @@ -7,7 +7,6 @@ securevalue: { pluralName: "SecureValues" current: "v1beta1" scope: "Namespaced" - current: "v1beta1" codegen: { ts: { enabled: false diff --git a/apps/secret/pkg/apis/secret_manifest.go b/apps/secret/pkg/apis/secret_manifest.go index 12138bbf57b..e8ff6dbca3a 100644 --- a/apps/secret/pkg/apis/secret_manifest.go +++ b/apps/secret/pkg/apis/secret_manifest.go @@ -7,6 +7,7 @@ package apis import ( "fmt" + "strings" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" @@ -19,25 +20,23 @@ var () var appManifestData = app.ManifestData{ AppName: "secret", Group: "secret.grafana.app", - Kinds: []app.ManifestKind{ + Versions: []app.ManifestVersion{ { - Kind: "SecureValue", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ + Name: "v1beta1", + Served: true, + Kinds: []app.ManifestVersionKind{ { - Name: "v1beta1", + Kind: "SecureValue", + Plural: "SecureValues", + Scope: "Namespaced", + Conversion: false, }, - }, - }, - { - Kind: "Keeper", - Scope: "Namespaced", - Conversion: false, - Versions: []app.ManifestKindVersion{ { - Name: "v1beta1", + Kind: "Keeper", + Plural: "Keepers", + Scope: "Namespaced", + Conversion: false, }, }, }, @@ -63,3 +62,16 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] return goType, exists } + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/conf/defaults.ini b/conf/defaults.ini index ccd2ba2d73a..889677b651a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -992,7 +992,7 @@ session_duration = "15m" # Set the plugins that will receive AWS settings for each request (via plugin context) # By default this will include all Grafana Labs owned AWS plugins, or those that make use of AWS settings (ElasticSearch, Prometheus). -forward_settings_to_plugins = cloudwatch, grafana-athena-datasource, grafana-redshift-datasource, grafana-x-ray-datasource, grafana-timestream-datasource, grafana-iot-sitewise-datasource, grafana-iot-twinmaker-app, grafana-opensearch-datasource, aws-datasource-provisioner, elasticsearch, prometheus, grafana-amazonprometheus-datasource, grafana-aurora-datasource +forward_settings_to_plugins = cloudwatch, grafana-athena-datasource, grafana-redshift-datasource, grafana-x-ray-datasource, grafana-timestream-datasource, grafana-iot-sitewise-datasource, grafana-iot-twinmaker-datasource, grafana-opensearch-datasource, aws-datasource-provisioner, elasticsearch, prometheus, grafana-amazonprometheus-datasource, grafana-aurora-datasource #################################### Azure ############################### [azure] @@ -2016,6 +2016,7 @@ enable = [feature_toggles.openfeature] # This is EXPERIMENTAL. Please, do not use this section +enable_api = true provider = static [feature_toggles.openfeature.context] @@ -2170,6 +2171,8 @@ frontend_poll_interval = 2s # With "paused", all Alert Rules will be created in Paused state. This is helpful to avoid double notifications. # With "unchanged", all Alert Rules will be created with the pause state unchanged coming from the source instance. alert_rules_state = "paused" +# Either "db" to store snapshots in the database or "fs" to store in the file system. +resource_storage_type = "db" ###################################### Secrets Manager ###################################### [secrets_manager] diff --git a/conf/sample.ini b/conf/sample.ini index 238162d2590..30e5bb42f6a 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -2068,6 +2068,8 @@ default_datasource_uid = # With "paused", all Alert Rules will be created in Paused state. This is helpful to avoid double notifications. # With "unchanged", all Alert Rules will be created with the pause state unchanged coming from the source instance. ;alert_rules_state = "paused" +# Either "db" to store snapshots in the database or "fs" to store in the file system. +;resource_storage_type = "db" ###################################### Secrets Manager ###################################### [secrets_manager] diff --git a/contribute/ISSUE_TRIAGE.md b/contribute/ISSUE_TRIAGE.md index b856798fb5c..b8b669b27b5 100644 --- a/contribute/ISSUE_TRIAGE.md +++ b/contribute/ISSUE_TRIAGE.md @@ -234,7 +234,6 @@ In case there is an uncertainty around the prioritization of an issue, please as **Critical bugs** 1. If a bug has been categorized and any of the following criteria apply, the bug should be labeled as critical and must be actively worked on as someone's top priority right now: - - Results in any data loss - Critical security or performance issues - Problem that makes a feature unusable diff --git a/contribute/architecture/k8s-inspired-backend-arch.md b/contribute/architecture/k8s-inspired-backend-arch.md index 520ebcfc559..84197d93d85 100644 --- a/contribute/architecture/k8s-inspired-backend-arch.md +++ b/contribute/architecture/k8s-inspired-backend-arch.md @@ -14,22 +14,18 @@ The end goal is for the Resource APIs to become the only interface for managing ## 1. Key Differences from Legacy API Endpoints - **URL structure & versioning:** - - **Resource APIs:** Follow Kubernetes conventions (`/apis///namespaces///`). Includes explicit API versions (e.g., `v0alpha1`, `v1`, `v2beta1`) in the path, allowing for controlled evolution and multiple versions of a resource API to co-exist. - **Legacy APIs:** Variable path structures (e.g., `/api/dashboards/uid/:uid`, `/api/ruler/grafana/api/v1/rules/:uid/:uid`). Less explicit versioning. - **Resource schemas:** - - **Resource APIs:** Each resource has a well-defined schema (`spec`) and is wrapped with an envelope with common metadata, all APIs come with an always-in-sync OpenAPI spec. - **Legacy APIs:** Structures vary - **Namespacing / org context:** - - **Resource APIs:** Uses explicit namespaces in the path (`/namespaces//...`) mapping to Grafana Organization IDs in OSS/Enterprise and Grafana Cloud Stack IDs in Grafana Cloud for scoping. - **Legacy APIs:** Org context determined implicitly (session, API key), not usually part of the URL structure. - **Consistency of convenience features:** - - **Resource APIs:** Convenience features such as resource history, restore and observability-as-code tooling come out of the box; a single implementation of convenience features works across all resources because of the standardization - **Legacy APIs:** Different APIs support different convenience features; implementations are feature-specific diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index f13f7ffccb3..45805f71fb3 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -101,11 +101,12 @@ If you want to contribute to any of the plugins listed below (that are found wit - grafana-postgresql-datasource - grafana-pyroscope-datasource - grafana-testdata-datasource -- jaegar +- jaeger - mysql - parca - tempo - zipkin +- loki To build and watch all these plugins you can run the following command. Note this can be quite resource intensive as it will start separate build processes for each plugin. @@ -119,6 +120,16 @@ If, instead, you would like to build and watch a specific plugin you can run the yarn workspace dev ``` +If you want to run multiple specific plugins, you can use the following command. + +``` +yarn nx run-many -t dev --projects="@grafana-plugins/grafana-azure-monitor-datasource,@grafana-plugins/jaeger" +``` + +If you're unsure of the name of the plugins you'd like to run you can query nx with the following command to get a list of all plugins: + +`yarn nx show projects --projects="@grafana-plugins/*"` + Next, we'll explain how to build and run the web server that serves these frontend assets. ### Backend @@ -251,18 +262,20 @@ Each version of Playwright needs specific versions of browser binaries to operat yarn playwright install chromium ``` -To run all tests in a headless Chromium browser and display results in the terminal. This assumes you have Grafana running on port 3000. +The following script starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) (same server that is being used when running e2e tests in CI) on port 3001 and runs all the Playwright tests. The development server is provisioned with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources and apps. ``` yarn e2e:playwright ``` -The following script starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) (same server that is being used when running e2e tests in Drone CI) on port 3001 and runs the Playwright tests. The development server is provisioned with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources and apps. +You can run against an arbitrary instance by setting the `GRAFANA_URL` environment variable: ``` -yarn e2e:playwright:server +GRAFANA_URL=http://localhost:3000 yarn e2e:playwright ``` +Note this will not start a development server, so you must ensure that Grafana is running and accessible at the specified URL. + ## Configure Grafana for development The default configuration, `defaults.ini`, is located in the `conf` directory. diff --git a/contribute/feature-toggles.md b/contribute/feature-toggles.md index 2a5899e166b..8e81c9c0c11 100644 --- a/contribute/feature-toggles.md +++ b/contribute/feature-toggles.md @@ -15,3 +15,12 @@ Examples: - [Backend](https://github.com/grafana/grafana/blob/feb2b5878b3e3ec551d64872c35edec2a0187812/pkg/services/authn/clients/session.go#L57): Use the `IsEnabled` function and pass in your feature toggle. - [Frontend](https://github.com/grafana/grafana/blob/feb2b5878b3e3ec551d64872c35edec2a0187812/public/app/features/search/service/folders.ts#L14): Check the config for your feature toggle. + +## Enabling toggles in development + +Add the feature toggle to the feature_toggle section in your custom.ini, for example: + +``` +[feature_toggles] +localeFormatPreference=true +``` diff --git a/contribute/style-guides/e2e-plugins.md b/contribute/style-guides/e2e-plugins.md index 8d6ffaca61a..e3fafa36fcf 100644 --- a/contribute/style-guides/e2e-plugins.md +++ b/contribute/style-guides/e2e-plugins.md @@ -32,10 +32,12 @@ You can add Playwright end-to-end tests for plugins to the [`e2e-playwright/plug - `yarn e2e:playwright` runs all Playwright tests. Optionally, you can provide the `--project mysql` argument to run tests in a specific project. - The `yarn e2e:playwright` script assumes you have Grafana running on `localhost:3000`. You may change this with an environment variable: + The `yarn e2e:playwright` command starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and runs the Playwright tests. - `HOST=127.0.0.1 PORT=3001 yarn e2e:playwright` + You can run against an arbitrary instance by setting the `GRAFANA_URL` environment variable: - The `yarn e2e:playwright:server` starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and runs the Playwright tests. + `GRAFANA_URL=http://localhost:3000 yarn e2e:playwright` + + Note this will not start a development server, so you must ensure that Grafana is running and accessible at the specified URL. - You can provision the development server with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources, and apps. diff --git a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json new file mode 100644 index 00000000000..c92924966ce --- /dev/null +++ b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json @@ -0,0 +1,528 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 138, + "links": [], + "panels": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto", + "wrapText": false + }, + "filterable": true, + "inspect": false, + "wrapHeaderText": false + }, + "fieldMinMax": true, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + }, + { + "id": "min", + "value": 0 + }, + { + "id": "max", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Info" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Google this term", + "url": "https://google.com/search?q=${__value:percentencode}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Min" + }, + "properties": [] + }, + { + "matcher": { + "id": "byName", + "options": "Max" + }, + "properties": [] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [] + }, + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "auto" + } + }, + { + "id": "unit", + "value": "dateTimeFromNow" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Image" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "alt": "${__value}", + "type": "image" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/(Time|Min|Max|Info|State|Image)/" + }, + "properties": [ + { + "id": "custom.width", + "value": 110 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Info" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "pattern": "up", + "result": { + "color": "green", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": "down", + "result": { + "color": "red", + "index": 1 + } + }, + "type": "regex" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Min" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "continuous-YlRd" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Max" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "color", + "value": { + "mode": "continuous-purples" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "displayName", + "value": "State" + }, + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Long Text" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "auto", + "wrapText": true + } + }, + { + "id": "custom.width", + "value": 300 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Pills" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "pill" + } + }, + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Data Link" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "data-links" + } + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Product", + "url": "${__value.text}" + }, + { + "targetBlank": true, + "title": "Grafana", + "url": "https://grafana.com" + } + ] + }, + { + "id": "custom.width", + "value": 180 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Gauge" + }, + "properties": [] + } + ] + }, + "gridPos": { + "h": 15, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": [ + "Min", + "Max" + ], + "reducer": [ + "max" + ], + "show": true + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "random_walk_table" + }, + { + "csvContent": "Info,Image,Pills,Data Link,Long Text\ndown,https://placecats.com/millie/200/400,hello,https://grafana.com,\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse tempus et augue et lacinia. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec eu pretium tortor. Cras venenatis sapien sed mauris gravida, ut scelerisque est fringilla. Cras lorem diam, facilisis nec malesuada in, vulputate vel enim. Etiam fringilla nisi quis felis blandit tincidunt. Cras id lacus ornare, ullamcorper nisl eget, bibendum odio. Pellentesque imperdiet, leo a imperdiet venenatis, ligula risus venenatis quam, vel euismod magna nisi sit amet leo.\"\nup,https://placecats.com/neo/200/400,\"[1,2,3,\"\"foo\"\",\"\"bar\"\"]\",https://grafana.com/solutions/kubernetes/,\"Sed imperdiet eget diam sit amet fringilla. Curabitur quis lacus blandit, mollis diam non, accumsan tortor. Aliquam ac tellus eget dui facilisis tempor eu id nulla. Maecenas ultrices turpis eu elementum imperdiet. Fusce eget rhoncus mi, et egestas lectus. Mauris facilisis auctor enim sed malesuada. Maecenas placerat ultricies metus vitae viverra. In hac habitasse platea dictumst. Mauris ipsum nisl, dictum eu aliquam eleifend, rutrum id orci. Nullam eget dui et odio eleifend porttitor.\"\nup fast,https://placecats.com/bella/200/400,\"foo,1,4,beep\",https://k6.io/,\"Proin ac libero vulputate ex vulputate pharetra ut vel lacus. Phasellus quis dolor sed leo finibus scelerisque. Ut vel finibus leo, sed viverra ipsum. Suspendisse vitae rutrum arcu. Donec sed tellus vel lectus bibendum vestibulum. Sed eu felis non velit dictum pulvinar eu et leo. Aenean et dignissim arcu. Nam luctus at neque quis efficitur. Fusce tempus at nibh a imperdiet. Nullam malesuada ac magna at facilisis. Duis pretium aliquam eros. Donec pharetra dignissim dolor non bibendum. Ut gravida mi id urna tempus, at ullamcorper felis vulputate. Duis congue augue ex, sed finibus leo ornare ut. Mauris non quam sodales, dignissim lorem eget, tincidunt mauris. Aliquam ut velit auctor, vestibulum metus sed, mollis massa.\"\ndown fast,https://placecats.com/neo_2/200/400,\"foo,bar,baz,a longer one,bim\",https://grafana.com/products/cloud/,\"Nullam in pulvinar justo. Nunc dictum arcu ac pellentesque bibendum. Sed in erat turpis. Vestibulum eu orci ac ligula lobortis tempus. Fusce consectetur feugiat magna, eu tempor nibh vestibulum ac. Aliquam erat volutpat. Vivamus sit amet viverra enim. Quisque mollis odio nulla, nec vulputate sem placerat in. Etiam dolor sapien, pulvinar in accumsan at, consequat eget nisi. Nunc condimentum neque magna, congue consectetur dui efficitur interdum. Nam lobortis fringilla maximus. Vestibulum eu dui a velit condimentum eleifend consequat nec lectus.\"", + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Table - Kitchen Sink", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "Info", + "mode": "outerTabular" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "A": false + }, + "includeByName": {}, + "indexByName": { + "A": 9, + "Data Link": 8, + "Image": 5, + "Info": 1, + "Long Text": 7, + "Max A": 3, + "Min A": 2, + "Pills": 6, + "State A": 4, + "Time A": 0 + }, + "orderBy": [], + "orderByMode": "manual", + "renameByName": { + "A": "Gauge", + "Info": "", + "Max A": "Max", + "Min A": "Min", + "State A": "State", + "Time": "Some really long title that requires wrapping", + "Time A": "Time", + "img_url": "Cat" + } + } + } + ], + "type": "table" + }, + { + "id": 2, + "type": "table", + "title": "Empty Table Panel", + "gridPos": { + "x": 0, + "y": 0, + "h": 6, + "w": 24 + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "thresholds" + } + }, + "overrides": [] + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "scenarioId": "csv_content", + "refId": "A", + "csvContent": "a,b,c" + } + ], + "datasource": { + "uid": "PD8C576611E62080A", + "type": "grafana-testdata-datasource" + }, + "options": { + "showHeader": true, + "cellHeight": "sm", + "footer": { + "show": false, + "reducer": [ + "sum" + ], + "countRows": false, + "fields": "" + } + } + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [ + { + "baseFilters": [], + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "filters": [], + "name": "Filters", + "type": "adhoc" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Panel Tests - Table - Kitchen Sink", + "uid": "dcb9f5e9-8066-4397-889e-864b99555dbb", + "version": 2 +} diff --git a/devenv/docker/blocks/prometheus_high_card/go.mod b/devenv/docker/blocks/prometheus_high_card/go.mod index 32a39000351..53cd8058c27 100644 --- a/devenv/docker/blocks/prometheus_high_card/go.mod +++ b/devenv/docker/blocks/prometheus_high_card/go.mod @@ -1,6 +1,6 @@ module high-card -go 1.24.4 +go 1.24.5 require github.com/prometheus/client_golang v1.22.0 diff --git a/devenv/docker/blocks/prometheus_utf8/go.mod b/devenv/docker/blocks/prometheus_utf8/go.mod index ed5e88f860d..77bc82d2b2d 100644 --- a/devenv/docker/blocks/prometheus_utf8/go.mod +++ b/devenv/docker/blocks/prometheus_utf8/go.mod @@ -1,6 +1,6 @@ module utf8-support -go 1.24.4 +go 1.24.5 require ( github.com/prometheus/client_golang v1.22.0 diff --git a/devenv/docker/blocks/stateful_webhook/Dockerfile b/devenv/docker/blocks/stateful_webhook/Dockerfile index 42a1610a9a2..e6527450805 100644 --- a/devenv/docker/blocks/stateful_webhook/Dockerfile +++ b/devenv/docker/blocks/stateful_webhook/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.4 +FROM golang:1.24.5 ADD main.go /go/src/webhook/main.go diff --git a/devenv/frontend-service/README.md b/devenv/frontend-service/README.md index c81008cb3b0..60acc1e134e 100644 --- a/devenv/frontend-service/README.md +++ b/devenv/frontend-service/README.md @@ -16,3 +16,13 @@ On top of the main Grafana development dependencies, you will need installed: To start the stack, from the root of the Grafana project run `make frontend-service-up`. Tilt will orchestrate the webpack and docker builds, and then run the services with Docker compose. You can monitor it's progress and see logs with the URL to the Tilt console. Once done, you can access Grafana at `http://localhost:3000`. Quitting the process will not stop the service from running. Run `make frontend-service-down` when done to shut down the docker containers. + +### Bootdata unavailable + +To simulate the `/bootdata` endpoint being available, there are special control URLs you can visit that use cookies to control behaviour: + + - `/-/down` - Simulates the endpoint being unavailable for 60 seconds. + - `/-/down/:seconds` - Simulates the endpoint being unavailable for a custom number of seconds. + - `/-/up` - Restores the endpoint to being available. + +When unavailable, the API will return `HTTP 503 Service Unavailable` with a JSON payload. \ No newline at end of file diff --git a/devenv/frontend-service/backend.dockerfile b/devenv/frontend-service/backend.dockerfile index bfc89735d1a..7326802d3a6 100644 --- a/devenv/frontend-service/backend.dockerfile +++ b/devenv/frontend-service/backend.dockerfile @@ -1,5 +1,5 @@ ARG BASE_IMAGE=alpine:3.21 -ARG GO_IMAGE=golang:1.24.4-alpine +ARG GO_IMAGE=golang:1.24.5-alpine # ----- Go build stage FROM ${GO_IMAGE} AS go-dev-builder diff --git a/devenv/frontend-service/build-grafana.sh b/devenv/frontend-service/build-grafana.sh index 8b1ac338889..b3adbe16e1a 100644 --- a/devenv/frontend-service/build-grafana.sh +++ b/devenv/frontend-service/build-grafana.sh @@ -6,7 +6,7 @@ echo "Go build cache: $(go env GOCACHE), $(ls -1 $(go env GOCACHE) | wc -l) item # Need to build version into the binary so plugin compatibility works correctly VERSION=$(jq -r .version package.json) -go build \ +go build -v \ -ldflags "-X main.version=${VERSION}" \ -gcflags "all=-N -l" \ -o ./bin/grafana ./pkg/cmd/grafana diff --git a/devenv/frontend-service/nginx.conf b/devenv/frontend-service/nginx.conf index 5927c492e53..26d6c1f9968 100644 --- a/devenv/frontend-service/nginx.conf +++ b/devenv/frontend-service/nginx.conf @@ -9,10 +9,29 @@ upstream frontend { server frontend-service:3000; } +map "$request_method:$cookie_fs_unavailable" $reject_login { + default 0; + "POST:1" 1; +} + server { listen 80; server_name _; + location ~ ^/-/down/?$ { + add_header Set-Cookie "fs_unavailable=true; Max-Age=60; Path=/; HttpOnly" always; + return 302 $scheme://$http_host/; + } + + location ~ ^/-/down/(?\d+)/?$ { + add_header Set-Cookie "fs_unavailable=true; Max-Age=$age; Path=/; HttpOnly" always; + return 302 $scheme://$http_host/; + } + + location ~ ^/-/up/?$ { + return 302 $scheme://$http_host/-/down/0; + } + # Special‐case POST /login to backend, GET to frontend location = /login { proxy_set_header Host $host; @@ -20,6 +39,11 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + if ($reject_login) { + add_header Content-Type application/json always; + return 503 '{"code":"Loading", "message": "Soon!"}'; + } + if ($request_method = POST) { proxy_pass http://backend; break; @@ -37,6 +61,11 @@ server { # Cheat with app plugin paths and route them to the backend. These should come from # the Plugin CDN location ~ ^/(api|apis|bootdata|logout|public\/plugins\/grafana\-\w+\-app) { + if ($cookie_fs_unavailable) { + add_header Content-Type application/json always; + return 503 '{"code":"Loading", "message": "Soon!"}'; + } + proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index e25bdeeed09..d16f9b96c84 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -93,6 +93,7 @@ "rows-to-fields": (import '../dev-dashboards/transforms/rows-to-fields.json'), "shared_queries": (import '../dev-dashboards/panel-common/shared_queries.json'), "slow_queries_and_annotations": (import '../dev-dashboards/scenarios/slow_queries_and_annotations.json'), + "table_kitchen_sink": (import '../dev-dashboards/panel-table/table_kitchen_sink.json'), "table_pagination": (import '../dev-dashboards/panel-table/table_pagination.json'), "table_sparkline_cell": (import '../dev-dashboards/panel-table/table_sparkline_cell.json'), "table_tests": (import '../dev-dashboards/panel-table/table_tests.json'), diff --git a/devenv/local_cdn/default.conf b/devenv/local_cdn/default.conf index e35c16ee146..0c37c5ff410 100644 --- a/devenv/local_cdn/default.conf +++ b/devenv/local_cdn/default.conf @@ -19,6 +19,6 @@ server { add_header 'Access-Control-Allow-Headers' '*' always; add_header 'Access-Control-Allow-Credentials' 'true' always; - rewrite ^/grafana-oss/11.4.0-pre/public(.*)$ $1 last; + rewrite ^/grafana-oss/12.1.0-pre/public(.*)$ $1 last; } } diff --git a/docs/sources/administration/correlations/use-variables-and-transformations/index.md b/docs/sources/administration/correlations/use-variables-and-transformations/index.md index 6251d537352..8ba408e5b94 100644 --- a/docs/sources/administration/correlations/use-variables-and-transformations/index.md +++ b/docs/sources/administration/correlations/use-variables-and-transformations/index.md @@ -66,7 +66,6 @@ Instructions below show how to set up a link that can run metrics query for the ``` Two data sources are created: Source (emulating logs data source) and Target (emulating metrics data source): - - A correlation called “App metrics” is created targeting the Target data source with its UID. - The label and description are provided as text - Each correlation contains the following configuration: diff --git a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-ecs/index.md b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-ecs/index.md index c9192e585e6..1f37fa513eb 100644 --- a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-ecs/index.md +++ b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-ecs/index.md @@ -79,7 +79,6 @@ To configure Grafana for high availability: In this task you configure Grafana Enterprise to validate the license with AWS instead of Grafana Labs. 1. In AWS IAM, create an access policy with the following permissions: - - `"license-manager:CheckoutLicense"` - `"license-manager:ListReceivedLicenses"` - `"license-manager:GetLicenseUsage"` diff --git a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-eks/index.md b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-eks/index.md index 5199690faa3..1d57566cd61 100644 --- a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-eks/index.md +++ b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-eks/index.md @@ -89,7 +89,6 @@ For more information on Grafana High Availability setup, refer to [Set up Grafan In this task, you configure Grafana Enterprise to validate the license with AWS instead of Grafana Labs. 1. In AWS IAM, assign the following permissions to the Node IAM role (if you are using a Node Group), or the Pod Execution role (if you are using a Fargate profile): - - `"license-manager:CheckoutLicense"` - `"license-manager:ListReceivedLicenses"` - `"license-manager:GetLicenseUsage"` @@ -100,7 +99,6 @@ In this task, you configure Grafana Enterprise to validate the license with AWS For more information about AWS license permissions, refer to [Actions, resources, and condition keys for AWS License Manager](https://docs.aws.amazon.com/service-authorization/latest/reference/list_awslicensemanager.html). 1. Choose **one** of the following options to update the [license_validation_type](../../../../setup-grafana/configure-grafana/enterprise-configuration/#license_validation_type) configuration to `aws`: - - **Option 1:** Use `kubectl edit configmap grafana` to edit `grafana.ini` add the following section to the configuration: ``` diff --git a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-instance-outside-aws/index.md b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-instance-outside-aws/index.md index 9d1741d94a4..1399cde35f6 100644 --- a/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-instance-outside-aws/index.md +++ b/docs/sources/administration/enterprise-licensing/activate-aws-marketplace-license/activate-license-on-instance-outside-aws/index.md @@ -44,7 +44,6 @@ To install Grafana, refer to the documentation specific to your implementation. To retrieve your license, Grafana Enterprise requires access to your AWS account and license information. To grant access, create an IAM user in AWS with access to the license, and pass its credentials as environment variables on the host or container where Grafana is running. These environment variables allow Grafana to retrieve license details from AWS. 1. In the AWS License Manager service, create an IAM policy with the following permissions: - - `"license-manager:CheckoutLicense"` - `"license-manager:ListReceivedLicenses"` - `"license-manager:GetLicenseUsage"` @@ -93,7 +92,6 @@ To retrieve your license, Grafana Enterprise requires access to your AWS account 1. Attach the policy you created to the IAM user. 1. Add the following values as environment variables to the host or container running Grafana: - - AWS region - IAM user's access key ID - IAM user's secret access key diff --git a/docs/sources/administration/grafana-advisor/_index.md b/docs/sources/administration/grafana-advisor/_index.md index 28caf9b1953..38673e9e121 100644 --- a/docs/sources/administration/grafana-advisor/_index.md +++ b/docs/sources/administration/grafana-advisor/_index.md @@ -7,7 +7,6 @@ labels: - oss - cloud - enterprise - stage: experimental keywords: - grafana - grafana advisor @@ -17,7 +16,7 @@ keywords: # Grafana Advisor -{{< docs/experimental product="Grafana Advisor" featureFlag="grafanaAdvisor" >}} +{{< docs/public-preview product="Grafana Advisor" featureFlag="grafanaAdvisor" >}} ## Overview @@ -106,3 +105,100 @@ To resolve issues flagged by Grafana Advisor and maintain system reliability, fo - **Immediate Action:** Address "Action needed" items promptly to ensure system reliability - **Systematic Review:** After fixing flagged issues, use the "Refresh" button to confirm all checks pass - **Proactive Updates:** Address plugin update recommendations under "Investigation needed" even if they haven't caused failures yet + +## How to create an alert based on Advisor results + +This guide walks you through creating a Grafana alert that monitors Advisor check results and triggers when failures are detected. + +### Step 1: Create a service account and token + +1. Navigate to **Administration → Users and access → Service accounts** in your Grafana instance +2. Click **Add service account** +3. Provide a name (for example, "advisor-alert-service-account") +4. Set the role to **Admin** to ensure proper permissions +5. Click **Create** +6. In the service account details, click **Add service account token** +7. Provide a token name and set an appropriate expiration date +8. Click **Generate token** + +> **Important**: Copy the token value immediately and store it securely - you won't be able to see it again + +### Step 2: Set up the Grafana Infinity data source + +> **Important**: Use Infinity plugin >=v3.3.0 for the JQ parser used later. + +1. Go to **Connections → Add new connection** +2. Search for "Infinity" +3. If not installed, click **Install**. Wait for the plugin to be installed. +4. From the plugin page, click **Add new data source**. +5. Configure the data source: + - **Name**: Give it a descriptive name (e.g., "Advisor API") + - **Setup Authentication**: In the **Auth type**, select **Bearer Token**. In the **Auth details** section, paste the service account token from Step 1 and in the **Allowed hosts** section, write your Grafana app URL and click the "Add" button (e.g., `https://your-grafana-host.com`). +6. Click **Save & test** to verify the connection + +### Step 3: Create the alert rule + +Now you have everything you need to create an alert based on Advisor results. + +1. Navigate to **Alerting → Alert rules** +2. Click **New alert rule** +3. Provide a rule name (e.g., "Advisor Failures Alert") + +#### Configure the query + +1. **Data source**: Select the Infinity data source created in Step 3 +2. Configure the query settings: + - **Type**: JSON + - **Parser**: JQ + - **Source**: URL + - **Format**: Table + - **Method**: GET + - **URL**: Get this from the Advisor interface: + - Visit the Advisor in your Grafana instance + - Open browser Developer Tools (F12) → Network tab + - Look for a request ending with `/checks` + - Copy the full URL (format: `https:///apis/advisor.grafana.app/v0alpha1/namespaces//checks`) + +#### Configure parsing options + +**Rows/Root** (paste this JQ expression): + +```jq +.items | map({ + type: .metadata.labels["advisor.grafana.app/type"], + creationTimestamp: .metadata.creationTimestamp, + failuresCount: (.status.report.failures | length) +}) | group_by(.type) | map(sort_by(.creationTimestamp) | last) +``` + +This JQ query processes Grafana Advisor check data to get the most recent result for each check type. It transforms each check into a simplified object with type, timestamp, and failure count. +The result is a clean array showing the current state of each check type (data source, plugin, configuration, etc.) with their failure counts, perfect for alerting when any type has failures > 0. + +**Columns** (add these three columns): + +- **Selector**: `creationTimestamp`, **Format**: Time +- **Selector**: `failuresCount`, **Format**: Number +- **Selector**: `type`, **Format**: String + +#### Optional: Filter by check type + +If you want to alert only for specific check types: + +1. In the **Computed columns, Filter, Group by** section +2. Add a **Filter**: `type == "license"` (replace "license" with your desired check type) + +#### Set alert condition + +- **Alert condition**: Select "WHEN Last OF QUERY Is above 0" +- This will trigger when any check type has failures. +- Click on "Preview alert rule condition" to see the result of the query. + +#### Complete alert configuration + +Select your preferred evaluation (e.g. every 24 hours) and notification settings. + +### Step 4: Save the alert rule + +Click **Save** and check the alert is being triggered. + +Your alert is now configured to monitor Advisor results and notify you when failures are detected! diff --git a/docs/sources/administration/migration-guide/cloud-migration-assistant.md b/docs/sources/administration/migration-guide/cloud-migration-assistant.md index f6d349fb9a6..16bb16a93c4 100644 --- a/docs/sources/administration/migration-guide/cloud-migration-assistant.md +++ b/docs/sources/administration/migration-guide/cloud-migration-assistant.md @@ -136,7 +136,6 @@ After a snapshot is created, a list of resources appears with resource Type and 1. Use the assistant's real-time progress tracking to monitor the migration. The status changes to 'Uploaded to cloud' for resources successfully copied to the cloud. From Grafana v12.0, you can group and sort resources during and after the migration: - - Click **Name** to sort resources alphabetically. - Click **Type** to group and sort by resource type. - Click **Status** to group and sort by upload status (pending upload, uploaded successfully, or experienced errors). diff --git a/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md b/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md index de111d5abb8..e68083eb4a0 100644 --- a/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md +++ b/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md @@ -84,7 +84,6 @@ Migration of plugins is the first step when transitioning from Grafana OSS/Enter ``` The command provided above will carry out an HTTP request to this endpoint and accomplish several tasks: - - It issues a GET request to the `/api/plugins` endpoint of your Grafana OSS/Enterprise instance to retrieve a list of installed plugins. - It filters out the list to only include community plugins and those signed by external parties. - It extracts the plugin ID and version before storing them in a `plugins.json` file. @@ -111,7 +110,6 @@ Migration of plugins is the first step when transitioning from Grafana OSS/Enter Replace `` with your Grafana Cloud Access Policy Token. To create a new one, refer to Grafana Cloud [access policies documentation](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) This script iterates through each plugin listed in the `plugins.json` file: - - It constructs a POST request for each plugin to add it to the specified Grafana Cloud instance. - It reports back the response for each POST request to give you confirmation or information about any issues that occurred. @@ -260,7 +258,6 @@ Grizzly does not currently support Reports and Playlists as a resource, so you c ``` The command provided above will carry out an HTTP request to this endpoint and accomplish several tasks: - - It fetches an array of all the playlists available in the Grafana OSS/Enterprise instance. - It then iterates through each playlist to obtain the complete set of details. - Finally, it stores each playlist's specification as separate JSON files within a directory named `playlists` diff --git a/docs/sources/administration/plugin-management/_index.md b/docs/sources/administration/plugin-management/_index.md index d396fc0825a..b7c674b4b18 100644 --- a/docs/sources/administration/plugin-management/_index.md +++ b/docs/sources/administration/plugin-management/_index.md @@ -249,7 +249,6 @@ To enable backend communication between plugins: ``` This is a comma-separated list that uses glob matching. - - To allow access to all plugins that have a backend: ``` diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md index d00ad4f003d..f07c874902d 100644 --- a/docs/sources/alerting/alerting-rules/alerting-migration.md +++ b/docs/sources/alerting/alerting-rules/alerting-migration.md @@ -111,7 +111,6 @@ To convert data source-managed alert rules to Grafana managed alerts: 2. Navigate to the Data source-managed alert rules section and click **Import to Grafana-managed rules**. 3. Choose the **Import source** from which you want to import rules: - - Select **Existing data source-managed rules** to import rules from connected Mimir or Loki data sources with the ruler API enabled. - Select **Prometheus YAML file** to import rules by uploading a Prometheus YAML rule file. diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index 13709c0545d..df7abac34b0 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -276,7 +276,6 @@ To do this, you need to make sure that your alert rule is in the right evaluatio This is different to [mute timings](ref:mute-timings), which stop notifications from being delivered, but still allows for alert rule evaluation and the creation of alert instances. 1. In **Configure no data and error handling**, you can define the alerting behavior and alerting state for two scenarios: - - When the evaluation returns **No data** or all values are null. - When the evaluation returns **Error** or timeout. @@ -297,7 +296,6 @@ Complete the following steps to set up notifications. 1. Configure who receives a notification when an alert rule fires by either choosing **Select contact point** or **Use notification policy**. **Select contact point** - 1. Choose this option to select an existing [contact point](ref:contact-points). All notifications for this alert rule are sent to this contact point automatically and notification policies aren't used. @@ -305,7 +303,6 @@ Complete the following steps to set up notifications. 1. You can also optionally select a mute or active timing as well as groupings and timings to define when not to send notifications. **Use notification policy** - 1. Choose this option to use the [notification policy tree](ref:notification-policies) to handle alert notifications. All notifications for this alert rule are managed by the notification policy tree, which routes alerts based on their labels. diff --git a/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md b/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md index 375268e520c..ba16e57c14a 100644 --- a/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md +++ b/docs/sources/alerting/alerting-rules/create-recording-rules/create-data-source-managed-recording-rules.md @@ -38,7 +38,6 @@ Note that in data source-managed groups, the alert rules and recording rules wit - Verify that you have write permission to the Prometheus or Loki data source. Otherwise, you will not be able to create or update Grafana Mimir managed alerting rules. - For Grafana Mimir and Loki data sources, enable the ruler API by configuring their respective services. - - **Loki** - The `local` rule storage type, default for the Loki data source, supports only viewing of rules. To edit rules, configure one of the other rule storage types. - **Mimir** - use the `/prometheus` prefix. The Prometheus data source supports both Grafana Mimir and Prometheus, and Grafana expects that both the [Query API](/docs/mimir/latest/operators-guide/reference-http-api/#querier--query-frontend) and [Ruler API](/docs/mimir/latest/operators-guide/reference-http-api/#ruler) are under the same URL. You cannot provide a separate URL for the Ruler API. diff --git a/docs/sources/alerting/best-practices/_index.md b/docs/sources/alerting/best-practices/_index.md index 892bfdfb186..18bcf13121a 100644 --- a/docs/sources/alerting/best-practices/_index.md +++ b/docs/sources/alerting/best-practices/_index.md @@ -8,7 +8,7 @@ labels: - cloud - enterprise - oss -menuTitle: Best Practices +menuTitle: Best practices title: Grafana Alerting best practices weight: 170 --- diff --git a/docs/sources/alerting/best-practices/dynamic-labels.md b/docs/sources/alerting/best-practices/dynamic-labels.md index 155b496d00a..7a9586ea6d9 100644 --- a/docs/sources/alerting/best-practices/dynamic-labels.md +++ b/docs/sources/alerting/best-practices/dynamic-labels.md @@ -233,7 +233,6 @@ This setup reproduces label flapping and shows how dynamic label values affect a 1. Simulate a query (`$A`) that returns a noisy signal. Select **TestData** as the data source and configure the scenario. - - Scenario: Random Walk - Series count: 1 - Start value: 51 @@ -241,7 +240,6 @@ This setup reproduces label flapping and shows how dynamic label values affect a - Spread: 100 (ensures large changes between consecutive data points) 1. Add an expression. - - Type: Reduce - Input: A - Function: Last (to get the most recent value) diff --git a/docs/sources/alerting/best-practices/dynamic-thresholds.md b/docs/sources/alerting/best-practices/dynamic-thresholds.md index 56371657d7b..8cbc3ada2b0 100644 --- a/docs/sources/alerting/best-practices/dynamic-thresholds.md +++ b/docs/sources/alerting/best-practices/dynamic-thresholds.md @@ -146,7 +146,6 @@ You can use the [TestData data source](ref:testdata-data-source) to replicate th 1. Simulate a query (`$A`) that returns latencies for each service. Select **TestData** as the data source and configure the scenario. - - Scenario: Random Walk - Alias: latency - Labels: service=api-$seriesIndex @@ -179,7 +178,6 @@ You can use the [TestData data source](ref:testdata-data-source) to replicate th For details on CSV format requirements, see [table data examples](ref:table-data-example). 1. Add a new **Reduce** expression (`$C`). - - Type: Reduce - Input: A - Function: Mean @@ -188,7 +186,6 @@ You can use the [TestData data source](ref:testdata-data-source) to replicate th This calculates the average latency for each service: `api-0`, `api-1`, etc. 1. Add a new **Math** expression. - - Type: Math - Expression: `$C > $B` - Set this expression as the **alert condition**. diff --git a/docs/sources/alerting/best-practices/multi-dimensional-alerts.md b/docs/sources/alerting/best-practices/multi-dimensional-alerts.md index 4523140248c..f612df6aa37 100644 --- a/docs/sources/alerting/best-practices/multi-dimensional-alerts.md +++ b/docs/sources/alerting/best-practices/multi-dimensional-alerts.md @@ -116,7 +116,6 @@ You can quickly experiment with multi-dimensional alerts using the [**TestData** 1. Go to **Alerting** and create an alert rule 1. Select **TestData** as the data source. 1. Configure the TestData scenario - - Scenario: **Random Walk** - Labels: `cpu=cpu-$seriesIndex` - Series count: 3 diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md index e36a87f08fe..09ac3ee6bee 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md @@ -89,11 +89,9 @@ This section outlines a minimal setup to configure Amazon SNS with Alerting. ### 1. Create an SNS Topic and Email Subscriber 1. **Navigate to SNS in AWS Console**: - - Go to the [Amazon SNS Console](https://console.aws.amazon.com/sns/v3/home). 2. **Create a new topic**: - - On the **Topics** page, choose **"Create topic"**. - Select **"Standard"** as the type. - Enter a **Name** for your topic, e.g., `My-Topic`. @@ -110,11 +108,9 @@ This section outlines a minimal setup to configure Amazon SNS with Alerting. ### 2. Create an IAM Policy, User, and Access Key 1. **Navigate to IAM in AWS Console**: - - Go to the [IAM Console](https://console.aws.amazon.com/iam/home). 2. **Create a new policy**: - - On the **Policies** page, choose **"Create policy"**. - Switch to the **"JSON"** tab and paste the following policy, replacing `Resource` with your SNS topic ARN: @@ -134,7 +130,6 @@ This section outlines a minimal setup to configure Amazon SNS with Alerting. - Click **"Next"**, name it (e.g., `SNSPublishPolicy`), and click **"Create policy"**. 3. **Create a new IAM user and assign the policy** - - In the IAM Console, on the **Users** page, choose **"Create user"**. - Enter a **User name**, e.g., `alerting-sns-user`. - Click **"Next"**. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md index 02db8cf0172..b7226283d1b 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md @@ -53,7 +53,6 @@ For Grafana OSS, you enable email notifications by first configuring [SMTP setti 1. Configure SMTP settings. Within the `[smtp]` settings section, specify the following parameters: - - `enabled = true`: Enables SMTP. - `host`: The hostname or IP address of your SMTP server, and the port number of your SMTP server (commonly 25, 465, or 587). Default is `localhost:25`. - `user`: Your SMTP username (if authentication is required). diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-irm.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-irm.md index 510d4e9a4f2..6c18f9064c9 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-irm.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-irm.md @@ -94,7 +94,6 @@ To create the integration, follow the same steps as described in [Configure an O 1. Navigate to **Alerts & IRM** -> **IRM** -> **Integrations**. 1. Click **+ New integration**. 1. Select either **Alertmanager** or **Webhook** integration from the list. - - **Alertmanager** integration – Includes preconfigured IRM templates for processing Grafana and Prometheus alerts. - **Webhook** integration – Uses default IRM templates for general alert processing. diff --git a/docs/sources/alerting/configure-notifications/template-notifications/examples.md b/docs/sources/alerting/configure-notifications/template-notifications/examples.md index 23b69a9ce21..7927d367c94 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/examples.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/examples.md @@ -444,7 +444,6 @@ Use one of the following methods to include a dashboard link with the correct ti ``` These URLs include a time range based on the alert’s timing: - - `from`: One hour before the alert started. - `to`: The current time if the alert is firing, or the alert’s end time if resolved. diff --git a/docs/sources/alerting/configure-notifications/template-notifications/manage-notification-templates.md b/docs/sources/alerting/configure-notifications/template-notifications/manage-notification-templates.md index 3ebb73d6907..e449bb6ecff 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/manage-notification-templates.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/manage-notification-templates.md @@ -57,7 +57,6 @@ To add an existing notification template to your contact point, complete the fol 1. Click **Edit**. A dialog box opens where you can select notification templates. 1. Click **Select notification template** or **Enter custom message** to customize a template or message - - You can select an existing notification template and [preview](#preview-a-notification-template) it using the default payload. - You can also copy the notification template and use it in the **Enter custom message** tab. diff --git a/docs/sources/alerting/fundamentals/templates.md b/docs/sources/alerting/fundamentals/templates.md index efed6ad7cac..62674000b45 100644 --- a/docs/sources/alerting/fundamentals/templates.md +++ b/docs/sources/alerting/fundamentals/templates.md @@ -55,12 +55,10 @@ Use templating to customize, format, and reuse alert notification messages. Crea In Grafana, you have various options to template your alert notification messages: 1. [Alert rule annotations](#template-annotations) - - Annotations add extra information, like `summary` and `description`, to alert instances for notification messages. - Template annotations to display query values that are meaningful to the alert, for example, the server name or the threshold query value. 1. [Alert rule labels](#template-labels) - - Labels are used to differentiate an alert instance from all other alert instances. - Template labels to add an additional label based on a query value, or when the labels from the query are incomplete or not descriptive enough. - Avoid displaying query values in labels as this can create numerous alert instances—use annotations instead. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/_index.md b/docs/sources/alerting/set-up/provision-alerting-resources/_index.md index 23d5f06691f..90467ce5d6b 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/_index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/_index.md @@ -58,7 +58,6 @@ Choose from the options below to import (or provision) your Grafana Alerting res 1. [Use configuration files to provision your alerting resources](ref:alerting_file_provisioning), such as alert rules and contact points, through files on disk. {{< admonition type="note" >}} - - You cannot edit provisioned resources from files in the Grafana UI. - Provisioning with configuration files is not available in Grafana Cloud. {{< /admonition >}} diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md index 2a5f5d97211..e595928e91a 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md @@ -159,7 +159,6 @@ In this section, we'll create Terraform configurations for each alerting resourc ``` Replace the following field values: - - `` with the terraform name of the data source. - `` with the terraform name of the folder. @@ -233,13 +232,11 @@ In this section, we'll create Terraform configurations for each alerting resourc ``` Replace the following field values: - - `` with the name of the alert rule group. Note that the distinct Grafana resources are connected through `uid` values in their Terraform configurations. The `uid` value will be randomly generated when provisioning. To link the alert rule group with its respective data source and folder in this example, replace the following field values: - - `` with the terraform name of the previously defined data source. - `` with the terraform name of the previously defined folder. @@ -266,7 +263,6 @@ In this section, we'll create Terraform configurations for each alerting resourc ``` Replace the following field values: - - `` with the terraform name of the contact point. It will be used to reference the contact point in other Terraform resources. - `` with the email to receive alert notifications. @@ -332,7 +328,6 @@ In this section, we'll create Terraform configurations for each alerting resourc ``` Replace the following field values: - - `` with the name of the Terraform resource. It will be used to reference the mute timing in the Terraform notification policy tree. 1. Continue to add more Grafana resources or [use the Terraform CLI for provisioning](#provision-grafana-resources-with-terraform). @@ -363,7 +358,6 @@ In this section, we'll create Terraform configurations for each alerting resourc ``` To configure the mute timing and contact point previously created in the notification policy tree, replace the following field values: - - `` with the terraform name of the previously defined contact point. - `` with the terraform name of the previously defined mute timing. diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md index 7b7f90c097d..bd44477db06 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md @@ -140,7 +140,6 @@ To add a new annotation query to a dashboard, follow these steps: 1. If you don't want the annotation query toggle to be displayed in the dashboard, select the **Hidden** checkbox. 1. Select a color for the event markers. 1. In the **Show in** drop-down, choose one of the following options: - - **All panels** - The annotations are displayed on all panels that support annotations. - **Selected panels** - The annotations are displayed on all the panels you select. - **All panels except** - The annotations are displayed on all panels except the ones you select. diff --git a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/dashboards/build-dashboards/create-dashboard/index.md index 033972b0b12..b62595986a2 100644 --- a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/dashboards/build-dashboards/create-dashboard/index.md @@ -101,7 +101,6 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee {{< /shared >}} 1. In the dialog box that opens, do one of the following: - - Select one of your existing data sources. - Select one of the Grafana [built-in special data sources](ref:built-in-special-data-sources). - Click **Configure a new data source** to set up a new one (Admins only). @@ -127,7 +126,6 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee 1. Refer to the following documentation for ways you can adjust panel settings. While not required, most visualizations need some adjustment before they properly display the information that you need. - - [Configure value mappings](ref:configure-value-mappings) - [Visualization-specific options](ref:visualization-specific-options) - [Override field values](ref:override-field-values) diff --git a/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md b/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md index 65a72a97d16..638ed32d79e 100644 --- a/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md +++ b/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md @@ -105,13 +105,11 @@ To create a dashboard, follow these steps: {{< figure src="/media/docs/grafana/dashboards/screenshot-new-dashboard-v12.png" max-width="750px" alt="New dashboard" >}} 1. Under **Panel layout**, choose one of the following options: - - **Custom** - Position and size panels manually. The default selection. - **Auto grid** - Panels are automatically resized to create a uniform grid based on the column and row settings. 1. Click **+ Add visualization**. 1. In the dialog box that opens, do one of the following: - - Select one of your existing data sources. - Select one of the Grafana [built-in special data sources](ref:built-in-special-data-sources). - Click **Configure a new data source** to set up a new one (Admins only). @@ -137,7 +135,6 @@ To create a dashboard, follow these steps: 1. Refer to the following documentation for ways you can adjust panel settings. While not required, most visualizations need some adjustment before they properly display the information that you need. - - [Configure value mappings](ref:configure-value-mappings) - [Visualization-specific options](ref:visualization-specific-options) - [Override field values](ref:override-field-values) @@ -227,9 +224,7 @@ To configure repeats, follow these steps: 1. Expand the **Repeat options** section. 1. Select the **Repeat by variable**. 1. For panels only, set the following options: - - Under **Repeat direction**, choose one of the following: - - **Horizontal** - Arrange panels side-by-side. Grafana adjusts the width of a repeated panel. You can’t mix other panels on a row with a repeated panel. - **Vertical** - Arrange panels in a column. The width of repeated panels is the same as the original, repeated panel. @@ -276,14 +271,12 @@ To configure show/hide rules, follow these steps: 1. Select **Show** or **Hide** to set whether the panel, row, or tab is shown or hidden based on the rules outcome. 1. Click **+ Add rule**. 1. Select a rule type: - - **Query result** - Show or hide a panel based on query results. Choose from **Has data** and **No data**. For panels only. - **Template variable** - Show or hide the panel, row, or tab dynamically based on the variable value. Select a variable and operator and enter a value. - **Time range less than** - Show or hide the panel, row, or tab if the dashboard time range is shorter than the selected time frame. Select or enter a time range. 1. Configure the rule. 1. Under **Match rules**, select one of the following: - - **Match all** - The panel, row, or tab is shown or hidden only if _all_ the rules are matched. - **Match any** - The panel, row, or tab is shown or hidden if _any_ of the rules are matched. @@ -315,7 +308,6 @@ To edit dashboards, follow these steps: 1. Click in the area you want to work with to bring it into focus and display the associated options in the edit pane. 1. Do one of the following: - - For rows or tabs, make the required changes using the edit pane. - For panels, update the panel title, description, repeat options or show/hide rules in the edit pane. For more changes, click **Configure** and continue in **Edit panel** view. - For dashboards, update the dashboard title, description, grouping or panel layout. For more changes, click the settings (gear) icon in the top-right corner. @@ -355,7 +347,6 @@ To move or resize, follow these steps: 1. Navigate to the dashboard you want to update. 1. Toggle on the edit mode switch. 1. Do one of the following: - - Click the panel title and drag the panel to the new location. - Click and drag the lower-right corner of the panel to change the size of the panel. diff --git a/docs/sources/dashboards/build-dashboards/import-dashboards/index.md b/docs/sources/dashboards/build-dashboards/import-dashboards/index.md index 6378fe4288a..df660d1b351 100644 --- a/docs/sources/dashboards/build-dashboards/import-dashboards/index.md +++ b/docs/sources/dashboards/build-dashboards/import-dashboards/index.md @@ -40,7 +40,6 @@ To import a dashboard, follow these steps: 1. Click **Dashboards** in the primary menu. 1. Click **New** and select **Import** in the drop-down menu. 1. Perform one of the following steps: - - Upload a dashboard JSON file. - Paste a [Grafana.com dashboard](#discover-dashboards-on-grafanacom) URL or ID into the field provided. - Paste dashboard JSON text directly into the text area. diff --git a/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md b/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md index 3dd373f4c31..99f4cf3bf32 100644 --- a/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md +++ b/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md @@ -94,7 +94,6 @@ Add links to other dashboards at the top of your current dashboard. If you don't add any tags, Grafana includes links to all other dashboards. 1. Set link options: - - **Show as dropdown** – If you are linking to lots of dashboards, then you probably want to select this option and add an optional title to the dropdown. Otherwise, Grafana displays the dashboard links side by side across the top of your dashboard. - **Include current time range** – Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now - **Include current template variable values** – Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link. For more information, see [Dashboard URL variables](ref:dashboard-url-variables). @@ -118,7 +117,6 @@ Add a link to a URL at the top of your current dashboard. You can link to any av 1. In the **Tooltip** field, enter the tooltip you want the link to display when the user hovers their mouse over it. 1. In the **Icon** drop-down, choose the icon you want displayed with the link. 1. Set link options; by default, these options are enabled for URL links: - - **Include current time range** – Select this option to include the dashboard time range in the link. When the user clicks the link, the linked dashboard opens with the indicated time range already set. **Example:** https://play.grafana.org/d/000000010/annotations?orgId=1&from=now-3h&to=now - **Include current template variable values** – Select this option to include template variables currently used as query parameters in the link. When the user clicks the link, any matching templates in the linked dashboard are set to the values from the link. - **Open link in new tab** – Select this option if you want the dashboard link to open in a new tab or window. @@ -134,7 +132,6 @@ To edit, duplicate, or delete dashboard link, follow these steps: 1. Click **Settings**. 1. Go to the **Links** tab. 1. Do one of the following: - - **Edit** - Click the name of the link and update the link settings. - **Duplicate** - Click the copy link icon next to the link that you want to duplicate. - **Delete** - Click the red **X** next to the link that you want to delete, and then **Delete**. diff --git a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md index a58f0332e50..ea79a3686c6 100644 --- a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md +++ b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md @@ -58,7 +58,6 @@ Adjust dashboard time settings when you want to change the dashboard timezone, t 1. On the **Settings** page, scroll down to the **Time Options** section of the **General** tab. 1. Specify time settings as follows. - - **Time zone:** Specify the local time zone of the service or system that you are monitoring. This can be helpful when monitoring a system or service that operates across several time zones. - **Default:** Grafana uses the default selected time zone for the user profile, team, or organization. If no time zone is specified for the user profile, a team the user is a member of, or the organization, then Grafana uses the local browser time. - **Browser time:** The time zone configured for the viewing user browser is used. This is usually the same time zone as set on the computer. diff --git a/docs/sources/dashboards/create-manage-playlists/index.md b/docs/sources/dashboards/create-manage-playlists/index.md index 20dba1c7cc6..4f2d7831350 100644 --- a/docs/sources/dashboards/create-manage-playlists/index.md +++ b/docs/sources/dashboards/create-manage-playlists/index.md @@ -48,7 +48,6 @@ You can start a playlist in four different view modes. View modes determine how 1. Find the desired playlist and click **Start playlist**. 1. In the dialog box that opens, select one of the [four playlist modes](#playlist-modes) available. 1. Disable any dashboard controls that you don't want displayed while the list plays; these controls are enabled and visible by default. Select from: - - **Time and refresh** - **Variables** - **Dashboard links** @@ -100,7 +99,6 @@ You can edit a playlist including adding, removing, and rearranging the order of 1. Click **Dashboards** in the main menu. 1. Click **Playlists**. 1. Find the playlist you want to update and click **Edit playlist**. Do one or more of the following: - - Edit - Update the name and time interval. - Add dashboards - Search for dashboards by title or tag to add them to the playlist. - Rearrange dashboards - Click and drag the dashboards into your desired order. diff --git a/docs/sources/dashboards/create-reports/_index.md b/docs/sources/dashboards/create-reports/_index.md index 645efcfbb95..0e9650f35ca 100644 --- a/docs/sources/dashboards/create-reports/_index.md +++ b/docs/sources/dashboards/create-reports/_index.md @@ -147,7 +147,6 @@ To create a report, follow these steps: - [Recipients](#4-recipients) - [Attachments](#5-attachments) 1. Click one of the following buttons at the bottom of the **Schedule report** drawer: - - The menu icon to access the following options: - **Download CSV** - **Preview PDF** @@ -179,7 +178,6 @@ To create a report, follow these steps: - [Recipients](#4-recipients) - [Attachments](#5-attachments) 1. Click one of the following buttons at the bottom of the **Schedule report** drawer: - - The menu icon to access the following options: - **Download CSV** - **Preview PDF** @@ -358,7 +356,6 @@ You can also navigate to the list of all reports from the dashboard-specific lis To edit a report, follow these steps: 1. Do one of the following: - - In the main menu, click **Dashboards > Reporting**. - Navigate to the dashboard from which the report was generated and click **Share > Schedule report**. @@ -373,12 +370,10 @@ You can pause and resume sending reports from the report list view. To do this, follow these steps: 1. Do one of the following: - - In the main menu, click **Dashboards > Reporting**. - Navigate to the dashboard from which the report was generated and click **Share > Schedule report**. 1. On the row of the report you want to update, do one of the following: - - Click the pause icon - The report won't be sent according to its schedule until it's resumed. - Click the resume icon - The report resumes on its previous schedule. @@ -389,7 +384,6 @@ You can also pause or resume a report from **Update report** drawer. To delete a report, follow these steps: 1. Do one of the following: - - In the main menu, click **Dashboards > Reporting**. - Navigate to the dashboard from which the report was generated and click **Share > Schedule report**. diff --git a/docs/sources/dashboards/manage-dashboards/index.md b/docs/sources/dashboards/manage-dashboards/index.md index 67ec9e71044..e09f2837eaa 100644 --- a/docs/sources/dashboards/manage-dashboards/index.md +++ b/docs/sources/dashboards/manage-dashboards/index.md @@ -76,7 +76,6 @@ Folders help you organize and group dashboards, which is useful when you have ma 1. Click **Dashboards** in the primary menu. 1. Do one of the following: - - On the **Dashboards** page, click **New** and select **New folder** in the drop-down. - Click an existing folder and on the folder’s page, click **New** and select **New folder** in the drop-down. diff --git a/docs/sources/dashboards/share-dashboards-panels/_index.md b/docs/sources/dashboards/share-dashboards-panels/_index.md index 00966e787e1..1b606ed8d0d 100644 --- a/docs/sources/dashboards/share-dashboards-panels/_index.md +++ b/docs/sources/dashboards/share-dashboards-panels/_index.md @@ -259,7 +259,6 @@ To share a personalized, direct link to your panel within your organization, fol 1. Click **Copy link**. 1. Send the copied link to a Grafana user with authorization to view it. 1. (Optional) To [generate an image of the panel as a PNG file](ref:image-rendering), customize the image settings: - - **Width** - In pixels. The default is 1000. - **Height** - In pixels. The default is 500. - **Scale factor** - The default is 1. diff --git a/docs/sources/dashboards/use-dashboards/index.md b/docs/sources/dashboards/use-dashboards/index.md index c5bba9e9e6d..5d956b4c380 100644 --- a/docs/sources/dashboards/use-dashboards/index.md +++ b/docs/sources/dashboards/use-dashboards/index.md @@ -304,7 +304,6 @@ To edit or delete filters, follow these steps: 1. On the dashboard, click anywhere on the filter you want to change. 1. Do one of the following: - - To edit the operator or value of a filter, click anywhere on the filter and update it. ![Editing an ad hoc filter](/media/docs/grafana/dashboards/screenshot-edit-filters-v11.3.png) diff --git a/docs/sources/dashboards/variables/_index.md b/docs/sources/dashboards/variables/_index.md index 6ec5d4916ed..da7451271d2 100644 --- a/docs/sources/dashboards/variables/_index.md +++ b/docs/sources/dashboards/variables/_index.md @@ -15,57 +15,81 @@ weight: 800 # Variables +A variable is a placeholder for a value. +When you change the value, the element using the variable will change to reflect the new value. + +Variables are displayed as drop-down lists (or in some cases text fields) at the top of the dashboard. +These drop-down lists make it easy to update the variable value and thus change the data being displayed in your dashboard. + +For example, if you needed to monitor several servers, you _could_ make a dashboard for each server. +Or you could create one dashboard and use panels with variables like this one, where you can change the server using the variable selector: + +{{< figure src="/media/docs/grafana/dashboards/screenshot-selected-variables-v12.png" max-width="750px" alt="Variable drop-down open and two values selected" >}} + +Variables allow you to create more interactive dashboards. +Instead of hard-coding things like server, application, and sensor names in your metric queries, you can use variables in their place. +They're useful for administrators who want to allow Grafana viewers to adjust visualizations without giving them full editing permissions. + +Using variables also allows you to single-source dashboards. +If you have multiple identical data sources or servers, you can make one dashboard and use variables to change what you are viewing. +This simplifies maintenance and upkeep enormously. + {{< youtube id="mMUJ3iwIYwc" >}} +You can use variables in: + +- Data source queries +- [Panel repeating options](https://grafana.com/docs/grafana//panels-visualizations/configure-panel-options/#configure-repeating-panels) +- [Dashboard and panel links](https://grafana.com/docs/grafana//dashboards/build-dashboards/manage-dashboard-links/) +- Titles +- Descriptions +- [Transformations](https://grafana.com/docs/grafana//panels-visualizations/query-transform-data/transform-data/) + +To see variable settings, navigate to **Dashboard Settings > Variables**. +Click a variable in the list to see its settings. + +{{< docs/play title="Templating - Interactive dashboard" url="https://play.grafana.org/goto/B9Xog68Hg?orgId=1" >}} + +## Template variables {#templates} + +A _template_ is any query that contains a variable. +Queries with text that starts with `$` are templates. + +{{< admonition type="note">}} +In our documentation and in the application, we typically simply refer to a _template query_ as a _query_, but we often use the terms _variable_ and _template variable_ interchangeably. +{{< /admonition >}} + +For example, if you were administering a dashboard to monitor several servers, it could have panels that use template queries like this one: + +```text +groupByNode(movingAverage(apps.$app.$server.counters.requests.count, 10), 2, 'sum') +``` + +The following image shows a panel in edit mode using the query: + +{{< figure src="/media/docs/grafana/dashboards/screenshot-template-query-v12.1.png" max-width="750px" alt="A panel using a template query" >}} + +### Variables in URLs + +Variable values are always synced to the URL using [query parameter syntax](https://grafana.com/docs/grafana/latest/dashboards/variables/variable-syntax/#query-parameters), `var-=value`. +For example: + +```text +https://play.grafana.org/d/HYaGDGIMk/templating-global-variables-and-interpolation?orgId=1&from=now-6h&to=now&timezone=utc&var-Server=CCC&var-MyCustomDashboardVariable=Hello%20World%21 +``` + +In the preceding example, the variables and values are `var-Server=CCC` and `var-MyCustomDashboardVariable=Hello%20World%21`. + +## Additional examples + +The following dashboards in Grafana Play provide examples of template variables: + +- [Templating - Repeated panels](https://play.grafana.org/goto/yfZOReUNR?orgId=1) - Using query variables to control how many panels appear in a dashboard. +- [Templating - Nested Variables Drilldown](https://play.grafana.org/d/testdata-nested-variables-drilldown/) - Demonstrates how changing one variable value can change the values available in a nested variable. +- [Templating - Global variables and interpolation](https://play.grafana.org/d/HYaGDGIMk/) - Shows you how the syntax for Grafana variables works. + +## Next steps + The following topics describe how to add and manage variables in your dashboards: {{< section >}} - -A variable is a placeholder for a value. You can use variables in metric queries and in panel titles. So when you change -the value, using the dropdown at the top of the dashboard, your panel's metric queries will change to reflect the new value. - -Variables allow you to create more interactive and dynamic dashboards. Instead of hard-coding things like server, application, -and sensor names in your metric queries, you can use variables in their place. Variables are displayed as dropdown lists at the top of -the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard. - -{{< figure src="/static/img/docs/v50/variables_dashboard.png" alt="Variable drop-down open and two values selected" >}} - -{{< docs/play title="Templating - Global variables and interpolation" url="https://play.grafana.org/d/HYaGDGIMk/" >}} - -Variables are useful for administrators who want to allow Grafana viewers to adjust visualizations without giving them full editing permissions. Grafana viewers can use variables. - -Variables and templates also allow you to single-source dashboards. If you have multiple identical data sources or servers, you can make one dashboard and use variables to change what you are viewing. This simplifies maintenance and upkeep enormously. - -## Templates - -A _template_ is any query that contains a variable. - -For example, if you were administering a dashboard to monitor several servers, you _could_ make a dashboard for each server. Or you could create one dashboard and use panels with template queries like this one: - -``` -wmi_system_threads{instance=~"$server"} -``` - -Variable values are always synced to the URL using the syntax `var-=value`. - -## Additional Examples - -Variables are listed in drop-down lists across the top of the screen. Select different variables to see how the visualizations change. - -To see variable settings, navigate to **Dashboard Settings > Variables**. Click a variable in the list to see its settings. - -Variables can be used in titles, descriptions, text panels, and queries. Queries with text that starts with `$` are templates. Not all panels will have template queries. - -The following dashboards in Grafana Play provide examples of template variables: - -- [Templating, repeated panels](https://play.grafana.org/d/000000025/) - Using query variables to control how many panels appear. -- [Templated Dynamic Dashboard](https://play.grafana.org/d/000000056/) - Uses query variables, chained query variables, an interval variable, and a repeated panel. -- [Templating - Nested Variables Drilldown](https://play.grafana.org/d/testdata-nested-variables-drilldown/) - -## Variable best practices - -- Variable drop-down lists are displayed in the order they are listed in the variable list in Dashboard settings. -- Put the variables that you will change often at the top, so they will be shown first (far left on the dashboard). -- By default, variables don't have a default value. This means that the topmost value in the drop-down is always preselected. If you want to pre-populate a variable with an empty value, you can use the following workaround in the variable settings: - 1. Select the **Include All Option** checkbox. - 2. In the **Custom all value** field, enter a value like `+`. diff --git a/docs/sources/dashboards/variables/add-template-variables/index.md b/docs/sources/dashboards/variables/add-template-variables/index.md index 54dc20e2d47..38b2bbc27e1 100644 --- a/docs/sources/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/dashboards/variables/add-template-variables/index.md @@ -35,6 +35,7 @@ keywords: - nested - chained - linked + - best practices labels: products: - cloud @@ -121,13 +122,11 @@ To create a variable, follow these steps: If you don't enter a display name, then the drop-down list label is the variable name. 1. Choose a **Show on dashboard** option: - - **Label and value** - The variable drop-down list displays the variable **Name** or **Label** value. This is the default. - **Value:** The variable drop-down list only displays the selected variable value and a down arrow. - **Nothing:** No variable drop-down list is displayed on the dashboard. 1. Click one of the following links to complete the steps for adding your selected variable type: - - [Query](#add-a-query-variable) - [Custom](#add-a-custom-variable) - [Textbox](#add-a-text-box-variable) @@ -138,6 +137,13 @@ To create a variable, follow these steps: +### Variable best practices + +- Variable drop-down lists are displayed in the order in which they're listed in the **Variables** in dashboard settings, so put the variables that you will change often at the top, so they will be shown first (far left on the dashboard). +- By default, variables don't have a default value. This means that the topmost value in the drop-down list is always preselected. If you want to pre-populate a variable with an empty value, you can use the following workaround in the variable settings: + 1. Select the **Include All Option** checkbox. + 2. In the **Custom all value** field, enter a value like `+`. + ## Add a query variable Query variables enable you to write a data source query that can return a list of metric names, tag values, or keys. For example, a query variable might return a list of server names, sensor IDs, or data centers. The variable values change as they dynamically fetch options with a data source query. @@ -157,7 +163,6 @@ Query expressions are different for each data source. For more information, refe For more information about data sources, refer to [Add a data source](ref:add-a-data-source). 1. In the **Query type** drop-down list, select one of the following options: - - **Label names** - **Label values** - **Metrics** @@ -166,7 +171,6 @@ Query expressions are different for each data source. For more information, refe - **Classic query** 1. In the **Query** field, enter a query. - - The query field varies according to your data source. Some data sources have custom query editors. - Each data source defines how the variable values are extracted. The typical implementation uses every string value returned from the data source response as a variable value. Make sure to double-check the documentation for the data source. - Some data sources let you provide custom "display names" for the values. For instance, the PostgreSQL, MySQL, and Microsoft SQL Server plugins handle this by looking for fields named `__text` and `__value` in the result. Other data sources may look for `text` and `value` or use a different approach. Always remember to double-check the documentation for the data source. @@ -175,12 +179,10 @@ Query expressions are different for each data source. For more information, refe 1. (Optional) In the **Regex** field, type a regular expression to filter or capture specific parts of the names returned by your data source query. To see examples, refer to [Filter variables with a regular expression](#filter-variables-with-regex). 1. In the **Sort** drop-down list, select the sort order for values to be displayed in the dropdown list. The default option, **Disabled**, means that the order of options returned by your data source query is used. 1. Under **Refresh**, select when the variable should update options: - - **On dashboard load** - Queries the data source every time the dashboard loads. This slows down dashboard loading, because the variable query needs to be completed before dashboard can be initialized. - **On time range change** - Queries the data source every time the dashboard loads and when the dashboard time range changes. Use this option if your variable options query contains a time range filter or is dependent on the dashboard time range. 1. (Optional) Configure the settings in the [Selection Options](#configure-variable-selection-options) section: - - **Multi-value** - Enables multiple values to be selected at the same time. - **Include All option** - Enables an option to include all variables. @@ -200,7 +202,6 @@ For example, if you have server names or region names that never change, then yo You can include numbers, strings, or key/value pairs separated by a space and a colon. For example, `key1 : value1,key2 : value2`. 1. (Optional) Configure the settings in the [Selection Options](#configure-variable-selection-options) section: - - **Multi-value** - Enables multiple values to be selected at the same time. - **Include All option** - Enables an option to include all variables. @@ -249,7 +250,6 @@ _Data source_ variables enable you to quickly change the data source for an enti Leave this field empty to display all instances. 1. (Optional) Configure the settings in the [Selection Options](#configure-variable-selection-options) section: - - **Multi-value** - Enables multiple values to be selected at the same time. - **Include All option** - Enables an option to include all variables. @@ -271,7 +271,6 @@ You can use an interval variable as a parameter to group by time (for InfluxDB), 1. (Optional) Select on the **Auto option** checkbox if you want to add the `auto` option to the list. This option allows you to specify how many times the current time range should be divided to calculate the current `auto` time span. If you turn it on, then two more options appear: - - **Step count** - Select the number of times the current time range is divided to calculate the value, similar to the **Max data points** query option. For example, if the current visible time range is 30 minutes, then the `auto` interval groups the data into 30 one-minute increments. The default value is 30 steps. - **Min interval** - The minimum threshold below which the step count intervals does not divide the time. To continue the 30 minute example, if the minimum interval is set to 2m, then Grafana would group the data into 15 two-minute increments. diff --git a/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md b/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md index 6880eae661c..feb170a5c16 100644 --- a/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md +++ b/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md @@ -130,7 +130,6 @@ The following settings are specific to the Elasticsearch data source. - **Index name** - Use the index settings to specify a default for the `time field` and your Elasticsearch index's name. You can use a time pattern, for example `[logstash-]YYYY.MM.DD`, or a wildcard for the index name. When specifying a time pattern, the fixed part(s) of the pattern should be wrapped in square brackets. - **Pattern** - Select the matching pattern if using one in your index name. Options include: - - no pattern - hourly - daily diff --git a/docs/sources/datasources/elasticsearch/query-editor/index.md b/docs/sources/datasources/elasticsearch/query-editor/index.md index c6e56ed963d..fa20353a395 100644 --- a/docs/sources/datasources/elasticsearch/query-editor/index.md +++ b/docs/sources/datasources/elasticsearch/query-editor/index.md @@ -61,7 +61,6 @@ Metrics queries aggregate data and produce a variety of calculations such as cou - **Alias** - Aliasing only applies to **time series queries**, where the last group is `date histogram`. This is ignored for any other type of query. - **Metric** - Metrics aggregations include: - - count - see [Value count aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-valuecount-aggregation.html) - average - see [Avg aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-rate-aggregation.html) - sum - see [Sum aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html) @@ -78,7 +77,6 @@ You can select multiple metrics and group by multiple terms or filters when usin Use the **+ sign** to the right to add multiple metrics to your query. Click on the **eye icon** next to **Metric** to hide metrics, and the **garbage can icon** to remove metrics. - **Group by options** - Create multiple group by options when constructing your Elasticsearch query. Date histogram is the default option. Below is a list of options in the dropdown menu. - - terms - see [Terms aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html). - filter - see [Filter aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html). - geo hash grid - see [Geohash grid aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html). diff --git a/docs/sources/datasources/google-cloud-monitoring/_index.md b/docs/sources/datasources/google-cloud-monitoring/_index.md index 61b37b747fd..27bc7b390cd 100644 --- a/docs/sources/datasources/google-cloud-monitoring/_index.md +++ b/docs/sources/datasources/google-cloud-monitoring/_index.md @@ -83,7 +83,6 @@ If Grafana is running on a Google Compute Engine (GCE) virtual machine, when you Before you can request data from Google Cloud Monitoring, you must first enable necessary APIs on the Google end. 1. Open the Monitoring and Cloud Resource Manager API pages: - - [Monitoring API](https://console.cloud.google.com/apis/library/monitoring.googleapis.com) - [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com) diff --git a/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md b/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md index d50db2e9d43..0c958283f0e 100644 --- a/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md +++ b/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md @@ -34,15 +34,11 @@ This document provides instructions for configuring the InfluxDB data source and To configure the InfluxDB data source you must have the `Administrator` role. -{{< admonition type="note" >}} -Select the query language you want to use with InfluxDB before adding the InfluxDB data source. Configuration options differ based on query language type. -{{< /admonition >}} - InfluxData provides three query languages. Some key points to consider: -- SQL is only available for InfluxDB v3.x. - Flux is a functional data scripting language for InfluxDB 2.x. Refer to [Query InfluxDB with Flux](https://docs.influxdata.com/influxdb/cloud/query-data/get-started/query-influxdb/) for a basic guide on working with Flux. - InfluxQL is SQL-like query language developed by InfluxData. It doesn't support more advanced functions such as JOINs. +- SQL is only available for InfluxDB v3.x. To help choose the best language for your needs, refer to a [comparison of Flux vs InfluxQL](https://docs.influxdata.com/influxdb/v1.8/flux/flux-vs-influxql/) @@ -60,97 +56,123 @@ Complete the following steps to set up a new InfluxDB data source: You are taken to the **Settings** tab where you will configure the data source. -## InfluxDB common configuration options +## Configuration Options -The following configuration options apply to **all three query language options**. +The following is a list of configuration options for InfluxDB. + +![Name and Default settings for InfluxDB configuration](https://grafana.com/media/docs/influxdb/InfluxDB-ConfigV2-Name.png) + +The first option is to configure the name of your connection. - **Name** - Sets the name you use to refer to the data source in panels and queries. Examples: `InfluxDB-InfluxQL`, `InfluxDB_SQL`. - **Default** - Toggle to set as the default data source. -- **Query language** - Select the query language for your InfluxDB instance. The three options are: - - **InfluxQL** - SQL-like language for querying InfluxDB, with statements such as SELECT, FROM, WHERE, and GROUP BY that are familiar to SQL users. - - **SQL** - Native SQL language starting with InfluxDB v.3.0. Refer to InfluxData's [SQL reference documentation](https://docs.influxdata.com/influxdb/cloud-serverless/reference/sql/) for a list of supported statements, operators, and functions. - - **Flux** - Flux is a data scripting language developed by InfluxData that allows you to query, analyze, and act on data. Refer to [Get started with Flux](https://docs.influxdata.com/influxdb/cloud/query-data/get-started/) for guidance on using Flux. -**HTTP section:** +### URL and Authentication + +![URL and Authentication for InfluxDB configuration](https://grafana.com/media/docs/influxdb/InfluxDB-ConfigV2-URLAuth-Section.png) + +These settings identify the Influx instance and schema the data source is connecting to. - **URL** - The HTTP protocol, IP address, and port of your InfluxDB API. InfluxDB’s default API port is `8086`. +- **Product** - Select the product version of your Influx instance. +- **Query language** - Select the query language for your InfluxDB instance. This will determine the connection details needed in **Database Settings**. The three options are: + - **Flux** - Flux is a data scripting language developed by InfluxData that allows you to query, analyze, and act on data. Refer to [Get started with Flux](https://docs.influxdata.com/influxdb/cloud/query-data/get-started/) for guidance on using Flux. + - **InfluxQL** - SQL-like language for querying InfluxDB, with statements such as SELECT, FROM, WHERE, and GROUP BY that are familiar to SQL users. + - **SQL** - Native SQL language starting with **InfluxDB v.3.0**. Refer to InfluxData's [SQL reference documentation](https://docs.influxdata.com/influxdb/cloud-serverless/reference/sql/) for a list of supported statements, operators, and functions. + +{{< admonition type="note" >}} +_For InfluxQL only._ **Database + Retention Policy (DBRP) Mapping** must be configured before data can be queried for the following product versions: _Influx OSS 1.x_, _Influx OSS 2.x_, _Influx Enterprise 1.x_, _Influx Cloud (TSM)_, _Influx Cloud Serverless_ + +Refer to [Manage DBRP Mappings](https://docs.influxdata.com/influxdb/cloud/query-data/influxql/dbrp/) for guidance on setting this up via the CLI or API +{{< /admonition >}} + +#### Advanced HTTP Settings (Optional) + +Advanced HTTP Settings are optional settings that can be configured for more control over your data source. + - **Allowed cookies** - Defines which cookies are forwarded to the data source. All other cookies are deleted by default. - **Timeout** - Set an HTTP request timeout in seconds. -**Auth section:** +**Custom HTTP Headers** -- **Basic auth** - The most common authentication method. Use your InfluxData user name and password to authenticate. Toggling requires you to add the user and password under **Basic auth details**. -- **With credentials** - Toggle to enable credentials such as cookies or auth headers to be sent with cross-site requests. -- **TLS client auth** - Toggle to use client authentication. When enabled, add the `Server name`, `Client cert` and `Client key` under the **TLS/SSL auth details** section. The client provides a certificate that the server validates to establish the client’s trusted identity. The client key encrypts the data between client and server. -- **With CA cert** - Authenticate with a CA certificate. Follow the instructions of your CA (Certificate Authority) to download the certificate file. -- **Skip TLS verify** - Toggle to bypass TLS certificate validation. -- **Forward OAuth identity** - Forward the OAuth access token (and also the OIDC ID token if available) of the user querying the data source. - -**Basic auth details:** - -If you enable **Basic auth** under the Auth section you need to configure the following: - -- **User** - Add the username used to sign in to InfluxDB. -- **Password** - Defines the token you use to query the bucket defined in **Database**. Retrieve this from the [Tokens page](https://docs.influxdata.com/influxdb/v2.0/security/tokens/view-tokens/) in the InfluxDB UI. - -**TLS/SSL auth details:** - -TLS/SSL certificates are encrypted and stored in the Grafana database. - -- **CA cert** - If you toggle **With CA cert** add your self-signed cert here. -- **Server name** - Name of the server. Example: server1.domain.com -- **Client cert** - Add the client certificate. -- **Client key** - Add the client key. - -**Custom HTTP headers:** +Click **+ Add header** to add one or more HTTP headers. HTTP headers pass additional context and metadata about the request/response. - **Header** - Add a custom HTTP header. Select an option from the drop-down. Allows custom headers to be passed based on the needs of your InfluxDB instance. - **Value** - The value for the header. -**Private data source connect:** +#### Auth and TSL/SSL Settings (Optional) -- **Private data source connect** - _Only for Grafana Cloud users._ Private data source connect, or PDC, allows you to establish a private, secured connection between a Grafana Cloud instance, or stack, and data sources secured within a private network. Click the drop-down to locate the URL for PDC. For more information regarding Grafana PDC refer to [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/). +There are several authentication methods you can choose in the Authentication section. + +- **No Authentication** - Make the data source available without authentication. Grafana recommends using some type of authentication method. +- **Basic auth** - The most common authentication method. Use your Influx instance username and password to authenticate. +- **Forward OAuth identity** - Forward the OAuth access token (and also the OIDC ID token if available) of the user querying the data source. +- **With credentials** - Toggle to enable credentials such as cookies or auth headers to be sent with cross-site requests. + +TLS/SSL Certificates are encrypted and stored in the Grafana database. + +- **TLS client auth** - When enabled, add the `Server name`, `Client cert` and `Client key`. The client provides a certificate that the server validates to establish the client’s trusted identity. The client key encrypts the data between client and server. + - **Server name** - Name of the server. Example: `server1.domain.com` + - **Client cert** - Add the client certificate. + - **Client key** - Add the client key. +- **CA cert** - Authenticate with a CA certificate. When enabled, follow the instructions of your CA (Certificate Authority) to download the certificate file. +- **Skip TLS verify** - Toggle to bypass TLS certificate validation. + +### Database Settings + +![Database Settings for InfluxDB configuration](https://grafana.com/media/docs/influxdb/InfluxDB-ConfigV2-DBSettings.png) + +{{< admonition type="note" >}} +Setting the database for this data source **does not deny access to other databases**. The InfluxDB query syntax allows switching the database in the query. For example: `SHOW MEASUREMENTS ON _internal` or `SELECT * FROM "_internal".."database" LIMIT 10` + +To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +{{< /admonition >}} + +These settings identify the Influx database your data source will connect to. The required information will vary by the query language selected in **URL and Authentication**. Each query language uses a different set of connection details. + +The table below illustrates the details needed for each query language: + +| **Setting** | **Flux** | **InfluxQL** | **SQL** | +| -------------------------- | -------- | ------------ | -------- | +| **Bucket** or **Database** | ✓ | ✓ | ✓ | +| **Organization** | ✓ | | | +| **Password** or **Token** | ✓ | ✓ | ✓ | +| **User** | | ✓ | | + +- **Bucket** or **Database** - Sets the ID of the bucket to query. Refer to [View buckets](https://docs.influxdata.com/influxdb/v2.0/organizations/buckets/view-buckets/) in InfluxData's documentation on how to locate the list of available buckets and their corresponding IDs. +- **Organization** - Sets the [Influx organization](https://v2.docs.influxdata.com/v2.0/organizations/) used for Flux queries. Also used for the `v.organization` query macro. +- **Password** or **Token** - Specify the token used to query the bucket defined in **Database**. Retrieve this from the [Tokens page](https://docs.influxdata.com/influxdb/v2.0/security/tokens/view-tokens/) in the InfluxDB UI. +- **User** - Add the username used to sign in to InfluxDB. + +**For Flux** + +- **Default bucket** is optional. The [Influx bucket](https://v2.docs.influxdata.com/v2.0/organizations/buckets/) used for the `v.defaultBucket` macro in Flux queries. +- With Influx 2.0 products, use the [influx authentication token to function](https://v2.docs.influxdata.com/v2.0/security/tokens/create-token/). Token must be set as `Authorization` header with the value `Token `. +- For Influx 1.8, the token is `username:password`. + +#### Advanced Database Settings (Optional) + +Advanced Database Settings are optional settings that give you more control over the query experience. + +- **Min time interval** - Sets the minimum time interval for auto group-by. Grafana recommends setting this to match the data write frequency. For example, if your data is written every minute, it’s recommended to set this interval to 1 minute, so that each group contains data from each new write. The default is `10s`. Refer to [Min time interval](#min-time-interval) for format examples. +- **Max series** - Sets a limit on the maximum number of series or tables that Grafana processes. Set a lower limit to prevent system overload, or increase it if you have many small time series and need to display more of them. The default is `1000`. + +**For InfluxQL** + +- **HTTP method** - Sets the HTTP method used to query your data source. The POST method allows for larger queries that would return an error using the GET method. The default method is `POST`. +- **Autocomplete range** - Sets a time range limit for the query editor's autocomplete to reduce the execution time of tag filter queries. As a result, any tags not present within the defined time range will be filtered out. For example, setting the value to 12h will include only tag keys/values from the past 12 hours. This feature is recommended for use with very large databases, where significant performance improvements can be observed. + +**For SQL** + +- **Insecure Connection** - Toggle to disable gRPC TLS security. + +### Private Data Source Connect + +_For Grafana Cloud only._ Private data source connect (PDC) allows you to establish a private, secured connection between a Grafana Cloud instance, or stack, and data sources secured within a private network. Click the drop-down to locate the URL for PDC. For more information regarding Grafana PDC refer to [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/). Click **Manage private data source connect** to be taken to your PDC connection page, where you'll find your PDC configuration details. -Once you have added your connection settings, click **Save & test** to test the data source connection. - -### InfluxQL-specific configuration section - -The following settings are specific to the InfluxQL query language option. - -**InfluxQL InfluxDB details section:** - -- **Database** - Sets the ID of the bucket to query. Refer to [View buckets](https://docs.influxdata.com/influxdb/v2.0/organizations/buckets/view-buckets/) in InfluxData's documentation on how to locate the list of available buckets and their corresponding IDs. -- **User** - The user name used to sign in to InfluxDB. -- **Password** - Defines the token used to query the bucket defined in **Database**. Retrieve the password from the [Tokens page](https://docs.influxdata.com/influxdb/v2.0/security/tokens/view-tokens/) of the InfluxDB UI. -- **HTTP method** - Sets the HTTP method used to query your data source. The POST method allows for larger queries that would return an error using the GET method. The default method is `POST`. -- **Min time interval** - _(Optional)_ Sets the minimum time interval for auto group-by. Grafana recommends setting this to match the data write frequency. For example, if your data is written every minute, it’s recommended to set this interval to 1 minute, so that each group contains data from each new write. The default is `10s`. Refer to [Min time interval](#min-time-interval) for format examples. -- **Autocomplete range** - _(Optional)_ Sets a time range limit for the query editor's autocomplete to reduce the execution time of tag filter queries. As a result, any tags not present within the defined time range will be filtered out. For example, setting the value to 12h will include only tag keys/values from the past 12 hours. This feature is recommended for use with very large databases, where significant performance improvements can be observed. -- **Max series** - _(Optional)_ Sets a limit on the maximum number of series or tables that Grafana processes. Set a lower limit to prevent system overload, or increase it if you have many small time series and need to display more of them. The default is `1000`. - -### SQL-specific configuration section - -The following settings are specific to the SQL query language option. - -**SQL InfluxDB details section:** - -- **Database** - Specify the **bucket ID**. Refer to the **Buckets page** in the InfluxDB UI to locate the ID. -- **Token** The API token used for SQL queries. Generated on InfluxDB Cloud dashboard under [Load Data > API Tokens](https://docs.influxdata.com/influxdb/cloud-serverless/get-started/setup/#create-an-all-access-api-token) menu. -- **Insecure Connection** - Toggle to disable gRPC TLS security. -- **Max series** - _(Optional)_ Sets a limit on the maximum number of series or tables that Grafana processes. Set a lower limit to prevent system overload, or increase it if you have many small time series and need to display more of them. The default is `1000`. - -### Flux-specific configuration section - -The following settings are specific to the Flux query language option. - -**Flux InfluxDB details section:** - -- **Organization** - The [Influx organization](https://v2.docs.influxdata.com/v2.0/organizations/) used for Flux queries. Also used for the `v.organization` query macro. -- **Token** - The authentication token used for Flux queries. With Influx 2.0, use the [influx authentication token to function](https://v2.docs.influxdata.com/v2.0/security/tokens/create-token/). Token must be set as `Authorization` header with the value `Token `. For Influx 1.8, the token is `username:password`. -- **Default bucket** - _(Optional)_ The [Influx bucket](https://v2.docs.influxdata.com/v2.0/organizations/buckets/) used for the `v.defaultBucket` macro in Flux queries. -- **Min time interval** - Sets the minimum time interval for auto group-by. Grafana recommends aligning this setting with the data write frequency. For example, if data is written every minute, set the interval to 1 minute to ensure each group includes data from every new write. The default is `10s`. -- **Max series** - Sets a limit on the maximum number of series or tables that Grafana processes. Set a lower limit to prevent system overload, or increase it if you have many small time series and need to display more of them. The default is `1000`. +After you have added your connection settings, click **Save & test** to test the data source connection. ### Min time interval diff --git a/docs/sources/datasources/influxdb/template-variables/index.md b/docs/sources/datasources/influxdb/template-variables/index.md index 72d48bdccbf..7165889ea00 100644 --- a/docs/sources/datasources/influxdb/template-variables/index.md +++ b/docs/sources/datasources/influxdb/template-variables/index.md @@ -46,9 +46,9 @@ refs: destination: /docs/grafana//dashboards/variables/add-template-variables/#add-a-query-variable variable-best-practices: - pattern: /docs/grafana/ - destination: /docs/grafana//dashboards/variables/#variable-best-practices + destination: /docs/grafana//dashboards/variables/add-template-variables/#variable-best-practices - pattern: /docs/grafana-cloud/ - destination: /docs/grafana-cloud/visualizations/dashboards/variables/#variable-best-practices + destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#variable-best-practices --- # InfluxDB template variables diff --git a/docs/sources/datasources/mysql/query-editor/_index.md b/docs/sources/datasources/mysql/query-editor/_index.md index 04a6cdc5e4e..8ec12f2789e 100644 --- a/docs/sources/datasources/mysql/query-editor/_index.md +++ b/docs/sources/datasources/mysql/query-editor/_index.md @@ -89,7 +89,6 @@ The following components will help you build a MySQL query: - **Format** - Select a format response from the drop-down for the MySQL query. The default is **Table**. If you use the **Time series** format option, one of the columns must be `time`. - **Dataset** - Select a database to query from the drop-down. - - **Table** - Select a table from the drop-down. Tables correspond to the chosen database. - **Data operations** - _Optional_ Select an aggregation from the drop-down. You can add multiple data operations by clicking the **+ sign**. Click the **X** to remove a data operation. Click the **garbage can icon** to remove the entire column. @@ -99,15 +98,12 @@ The following components will help you build a MySQL query: - **Alias** - _Optional_ Add an alias from the drop-down. You can also add your own alias by typing it in the box and clicking **Enter**. Remove an alias by clicking the **X**. - **Filter** - Toggle to add filters. - - **Filter by column value** - _Optional_ If you toggle **Filter** you can add a column to filter by from the drop-down. To filter on more columns, click the **+ sign** to the right of the condition drop-down. You can choose a variety of operators from the drop-down next to the condition. When multiple filters are added you can add an `AND` operator to display all true conditions or an `OR` operator to display any true conditions. Use the second drop-down to choose a filter. To remove a filter, click the `X` button next to that filter's drop-down. After selecting a date type column, you can choose **Macros** from the operators list and select `timeFilter` which will add the `$\_\_timeFilter` macro to the query with the selected date column. - **Group** - Toggle to add **Group by column**. - - **Group by column** - Select a column to filter by from the drop-down. Click the **+ sign** to filter by multiple columns. Click the **X** to remove a filter. - **Order** - Toggle to add an ORDER BY statement. - - **Order by** - Select a column to order by from the drop-down. Select ascending (`ASC`) or descending (`DESC`) order. - **Limit** - You can add an optional limit on the number of retrieved results. Default is 50. diff --git a/docs/sources/datasources/prometheus/configure/_index.md b/docs/sources/datasources/prometheus/configure/_index.md index d312245e658..b68219e3866 100644 --- a/docs/sources/datasources/prometheus/configure/_index.md +++ b/docs/sources/datasources/prometheus/configure/_index.md @@ -148,7 +148,6 @@ Use the IP address of the Prometheus container, or the hostname if you are using There are three authentication options for the Prometheus data source. - **Basic authentication** - The most common authentication method. - - **User** - The username you use to connect to the data source. - **Password** - The password you use to connect to the data source. diff --git a/docs/sources/datasources/prometheus/query-editor/_index.md b/docs/sources/datasources/prometheus/query-editor/_index.md index 31919d45e05..5be3b7cc180 100644 --- a/docs/sources/datasources/prometheus/query-editor/_index.md +++ b/docs/sources/datasources/prometheus/query-editor/_index.md @@ -78,7 +78,6 @@ The following video demonstrates how to use the visual Prometheus query builder: Builder mode contains the following components: - **Kick start your query** - Click to view a list of predefined operation patterns that help you quickly build queries with multiple operations. These include: - - Rate query starters - Histogram query starters - Binary query starters @@ -104,7 +103,6 @@ Click **+ Operations** to select from a list of operations including Aggregation **Options:** - **Legend**- Lets you customize the name for the time series. You can use a predefined or custom format. - - **Auto** - Displays unique labels. Also displays all overlapping labels if a series has multiple labels. - **Verbose** - Displays all label names. - **Custom** - Lets you customize the legend using label templates. For example, `{{hostname}}` is replaced with the value of the `hostname` label. To switch to a different legend mode, clear the input and click outside the field. @@ -112,7 +110,6 @@ Click **+ Operations** to select from a list of operations including Aggregation - **Min step** - Sets the minimum interval between data points returned by the query. For example, setting this to `1h` suggests that data is collected or displayed at hourly intervals. This setting supports the `$__interval` and `$__rate_interval` macros. Note that the time range of the query is aligned to this step size, which may adjust the actual start and end times of the returned data. - **Format** - Determines how the data from your Prometheus query is interpreted and visualized in a panel. Choose from the following format options: - - **Time series** - The default format. Refer to [Time series kind formats](https://grafana.com/developers/dataplane/timeseries/) for information on time series data frames and how time and value fields are structured. - **Table** - Displays data in table format. This format works only in a [Table panel](ref:table). - **Heatmap** - Displays Histogram-type metrics in a [Heatmap panel](ref:heatmap) by converting cumulative histograms to regular ones and sorting the series by the bucket bound. Converts cumulative histogram data into regular histogram format and sorts the series by bucket boundaries for proper display. diff --git a/docs/sources/datasources/tempo/configure-tempo-data-source.md b/docs/sources/datasources/tempo/configure-tempo-data-source.md index 39d3cdf751a..620459d765b 100644 --- a/docs/sources/datasources/tempo/configure-tempo-data-source.md +++ b/docs/sources/datasources/tempo/configure-tempo-data-source.md @@ -235,7 +235,6 @@ To use custom queries with the configuration, follow these steps: 1. Specify a custom query to be used to query metrics data. Each linked query consists of: - - **Link Label:** _(Optional)_ Descriptive label for the linked query. - **Query:** The query ran when navigating from a trace to the metrics data source. Interpolate tags using the `$__tags` keyword. @@ -321,6 +320,22 @@ You can configure this setting as follows: | **Time shift start** | Time shift for start of search. Default: `30m`. | | **Time shift end** | Time shift for end of search. Default: `30m`. | +### Tags time range + +The **Tags time range** feature controls how tag and tag-value queries are executed by specifying the time window applied to these requests. You can select one of the following options to constrain your queries: + +| Name | Description | +| ------------------- | ---------------------------------- | +| **Last 30 minutes** | Last 30 minutes of selected range. | +| **Last 3 hours** | Last 3 hours of selected range. | +| **Last 24 hours** | Last 24 hours of selected range. | +| **Last 3 days** | Last 3 days of selected range. | +| **Last 7 days** | Last 7 days of selected range. | + +### Tag limit + +The **Tag limit** setting modifies the max number of tags and tag values to retrieve from Tempo. Default: 5000 + ### Span bar The **Span bar** setting helps you display additional information in the span bar row. @@ -333,10 +348,6 @@ You can choose one of three options: | **Duration** | _(Default)_ Displays the span duration on the span bar row. | | **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `component`. | -### Tag limit - -The **Tag limit** setting modifies the max number of tags and tag values to retrieve from Tempo. Default: 5000 - ### Private data source connect [//]: # 'Shared content for authentication section procedure in data sources' diff --git a/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md b/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md index 7b28419d2d5..c8f4bc4b150 100644 --- a/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md +++ b/docs/sources/datasources/tempo/traces-in-grafana/trace-correlations.md @@ -45,7 +45,6 @@ To use trace correlations, you need: 1. On step 1, provide a **label** for the correlation, and an optional **description**. 1. On step 2, configure the correlation **target**. - - Select the **Type** drop-down list and choose **Query** to link to another data source or choose **External** for a custom URL. - For a query **Target**, select the target drop-down list and select the data source that should be queried when the link is clicked. Define the target query. @@ -68,7 +67,6 @@ To use trace correlations, you need: {{< figure src="/media/docs/tempo/screenshot-grafana-trace-correlations-loki-step-2.png" max-width="900px" class="docs-image--no-shadow" alt="Setting up a correlation for a Loki target using trace variables" >}} 1. On step 3, configure the correlation data source: - - Select your Tempo data source in the **Source** drop-down list. - Enter the trace data variable you use for the correlation in the **Results field**. @@ -104,7 +102,6 @@ In this example, you configure trace to logs by service name and a trace identif {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations-example-1-step-1.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} 1. On step 2, configure the correlation target: - - Select the target type **Query** and select your Loki data source as **Target**. - Define the Loki query, using `serviceName` and `traceID` as variables derived from the span data: @@ -116,7 +113,6 @@ In this example, you configure trace to logs by service name and a trace identif {{< figure src="/media/docs/tempo/screenshot-grafana-trace-view-correlations-example-1-step-2.png" max-width="900px" class="docs-image--no-shadow" alt="Using correlations for a trace" >}} 1. On step 3, configure the correlation source: - - Select your Tempo data source as **Source**. - Use `traceID` as **Results field**. @@ -138,7 +134,6 @@ In this example, you configure trace corrections with a custom URL. 1. On step 1, add a new correlation with the label **Open custom URL** and an optional description. 1. On step 2, configure the correlation target: - - Select the target type **External**. - Define your target URL, using variables derived from the span data. In this example, we are using `serviceName` and `traceID`. @@ -148,7 +143,6 @@ In this example, you configure trace corrections with a custom URL. ``` 1. On step 3, configure the correlation source: - - Select your Tempo data source as **Source**. - Use `traceID` as **Results field**. diff --git a/docs/sources/datasources/testdata/_index.md b/docs/sources/datasources/testdata/_index.md index fb03cebd5f5..e909f4d43f2 100644 --- a/docs/sources/datasources/testdata/_index.md +++ b/docs/sources/datasources/testdata/_index.md @@ -126,13 +126,9 @@ This feature is experimental and requires Grafana version 10.3.0 or later. If you want to use a version of TestData different from the one shipped with Grafana, follow these steps: -1. Enable the [feature toggle](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) `externalCorePlugins`. 1. Set the configuration field `as_external` for the plugin to `true`. An example configuration would be: ```ini - [feature_toggles] - externalCorePlugins = true - [plugin.grafana-testdata-datasource] as_external = true ``` diff --git a/docs/sources/explore/get-started-with-explore.md b/docs/sources/explore/get-started-with-explore.md index d42862432d4..c4a64bc9196 100644 --- a/docs/sources/explore/get-started-with-explore.md +++ b/docs/sources/explore/get-started-with-explore.md @@ -53,7 +53,6 @@ Explore consists of a toolbar, outline, query editor, the ability to add multipl - **Outline** - Keeps track of the queries and visualization panels created in Explore. Refer to [Content outline](#content-outline) for more detail. - **Toolbar** - Provides quick access to frequently used tools and settings. - - **Data source picker** - Select a data source from the dropdown menu, or use absolute time. - **Split** - Click to compare visualizations side by side. Refer to [Split and compare](#split-and-compare) for additional detail. - **Add** - Click to add your exploration to a dashboard. You can also use this to declare an incident,create a forecast, detect outliers and to run an investigation. @@ -204,5 +203,5 @@ Now that you are familiar with Explore you can: - [Build dashboards](https://grafana.com/docs/grafana//dashboards/build-dashboards/) - Create a wide variety of [visualizations](https://grafana.com/docs/grafana//panels-visualizations/visualizations/) - [Work with logs](https://grafana.com/docs/grafana//explore/logs-integration/) -- [Work with traces](https://grafana.com/docs/grafana/) +- [Work with traces](https://grafana.com/docs/grafana//explore/trace-integration/) - [Create and use correlations](https://grafana.com/docs/grafana//explore/correlations-editor-in-explore/) diff --git a/docs/sources/fundamentals/exemplars/index.md b/docs/sources/fundamentals/exemplars/index.md index 4181c50cac5..8b556d928d6 100644 --- a/docs/sources/fundamentals/exemplars/index.md +++ b/docs/sources/fundamentals/exemplars/index.md @@ -101,7 +101,6 @@ This panel shows the details of the trace in different segments. - The next segment shows the entire span for the specific trace as a narrow strip. All levels of the trace from the client all the way down to database query is displayed, which provides a bird's eye view of the time distribution across all layers over which the HTTP request was processed. - 1. You can click within this strip view to display a magnified view of a smaller time segment within the span. This magnified view shows up in the bottom segment of the panel. 1. In the magnified view, you can expand or collapse the various levels of the trace to drill down to the specific span of interest. diff --git a/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md b/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md index 341fbd2af8c..6ae3821e58a 100644 --- a/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md +++ b/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md @@ -167,7 +167,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.24.4 + go-version: 1.24.5 - name: Verify Go version run: go version @@ -208,7 +208,7 @@ This GitHub Action automates the deployment of a Grafana dashboard using the Fou The first few steps: - Check out the repository to access the project code. -- Install Go 1.24.4 using the `actions/setup-go` action. +- Install Go 1.24.5 using the `actions/setup-go` action. - Verify Go is properly installed. ### 2. Download and install `grafanactl` diff --git a/docs/sources/observability-as-code/get-started.md b/docs/sources/observability-as-code/get-started.md index 00b48501f95..ef9822aae5c 100644 --- a/docs/sources/observability-as-code/get-started.md +++ b/docs/sources/observability-as-code/get-started.md @@ -68,17 +68,14 @@ Refer to the [Foundation SDK](../foundation-sdk) documentation for more informat If you're already using established Infrastructure as Code or other configuration management tools, Grafana offers integrations to manage resources within your existing workflows. - [Terraform](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/terraform/) - - Use the Grafana Terraform provider to manage dashboards, alerts, and more. - Understand how to define and deploy resources using HCL/JSON configurations. - [Ansible](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/ansible/) - - Learn to use the Grafana Ansible collection to manage Grafana Cloud resources, including folders and cloud stacks. - Write playbooks to automate resource provisioning through the Grafana API. - [Grafana Operator](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/grafana-operator/) - - Utilize Kubernetes-native management with the Grafana Operator. - Manage dashboards, folders, and data sources via Kubernetes Custom Resources. - Integrate with GitOps workflows for seamless version control and deployment. diff --git a/docs/sources/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md index 4a81b71a197..b0c44727ffd 100644 --- a/docs/sources/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md @@ -87,7 +87,6 @@ This token needs to be added to your Git Sync configuration to enable read and w 1. Create a new token using [Create new fine-grained personal access token](https://github.com/settings/personal-access-tokens/new). Refer to [Managing your personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for instructions. 1. Under **Permissions**, expand **Repository permissions**. 1. Set these permissions for Git Sync: - - **Contents**: Read and write permission - **Metadata**: Read-only permission - **Pull requests**: Read and write permission diff --git a/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md index 8ab3afa9c03..e92bb0800dc 100644 --- a/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md +++ b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md @@ -97,7 +97,6 @@ Saving changes requires opening a pull request in your GitHub repository. 1. Click **Save dashboard**. 1. On the **Provisioned dashboard** panel, choose the options you want to use: - - **Update default refresh value**: Check this box to make the current refresh the new default. - **Update default variable values**: Check this box to make the current values the new default. - **Path**: Provide the path for your repository, ending in a JSON or YAML file. diff --git a/docs/sources/panels-visualizations/configure-panel-options/index.md b/docs/sources/panels-visualizations/configure-panel-options/index.md index 59c466a20fb..dc7c8601c11 100644 --- a/docs/sources/panels-visualizations/configure-panel-options/index.md +++ b/docs/sources/panels-visualizations/configure-panel-options/index.md @@ -90,7 +90,6 @@ To configure repeating panels, follow these steps: 1. Open the **Panel options** section of the panel editor pane. 1. Under **Repeat options**, select a variable in the **Repeat by variable** drop-down list. 1. Under **Repeat direction**, choose one of the following: - - **Horizontal** - Arrange panels side-by-side. Grafana adjusts the width of a repeated panel. You can't mix other panels on a row with a repeated panel. - **Vertical** - Arrange panels in a column. The width of repeated panels is the same as the original, repeated panel. diff --git a/docs/sources/panels-visualizations/configure-standard-options/index.md b/docs/sources/panels-visualizations/configure-standard-options/index.md index a27bd238a4f..2e737ed7de2 100644 --- a/docs/sources/panels-visualizations/configure-standard-options/index.md +++ b/docs/sources/panels-visualizations/configure-standard-options/index.md @@ -198,7 +198,6 @@ To display timestamps that are in seconds since epoch, multiply your timestamp v 1. Click **Add transformation**. 1. Select the **Add field from calculation** transformation. 1. Set the following options: - - **Mode** - **Binary operation** - **Operation** - Select your timestamp field diff --git a/docs/sources/panels-visualizations/configure-value-mappings/index.md b/docs/sources/panels-visualizations/configure-value-mappings/index.md index 82231549084..11223d3870b 100644 --- a/docs/sources/panels-visualizations/configure-value-mappings/index.md +++ b/docs/sources/panels-visualizations/configure-value-mappings/index.md @@ -184,7 +184,6 @@ The following image shows a table visualization with value mappings. If you want 1. Scroll to the **Value mappings** section and expand it. 1. Click **Add value mappings**. 1. Click **Add a new mapping** and then select one of the following: - - **Value** - Enter a single value to match. - **Range** - Enter the beginning and ending values of a range to match. - **Regex** - Enter a regular expression pattern to match. diff --git a/docs/sources/panels-visualizations/panel-inspector/index.md b/docs/sources/panels-visualizations/panel-inspector/index.md index a8086224d08..d751957a171 100644 --- a/docs/sources/panels-visualizations/panel-inspector/index.md +++ b/docs/sources/panels-visualizations/panel-inspector/index.md @@ -43,7 +43,6 @@ Grafana generates a CSV file that contains your data, including any transformati 1. Click **Data**. If your panel contains multiple queries or queries multiple nodes, then you have additional options. - - **Select result**: Choose which result set data you want to view. - **Transform data** - **Join by time**: View raw data from all your queries at once, one result set per column. Click a column heading to reorder the data. diff --git a/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md b/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md index 7d891f8e9b6..54fd4f8f6cc 100644 --- a/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md +++ b/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md @@ -32,7 +32,6 @@ For general information on Grafana expressions, refer to [Write expression queri ## Before you begin - Enable SQL expressions under the feature toggle `sqlExpressions`. - - If you self-host Grafana, you can find feature toggles in the configuration file `grafana.ini`. ``` @@ -150,6 +149,7 @@ The SQL conversion path: - Currently, only one SQL expression is supported per panel or alert. - Grafana supports certain data sources. Refer to [compatible data sources](#compatible-data-sources) for a current list. +- Autocomplete is available, but column/field autocomplete is only available after enabling the `sqlExpressionsColumnAutoComplete` feature toggle, which is provided on an experimental basis. ## Supported data source formats @@ -157,12 +157,10 @@ Grafana supports three types of data source response formats: 1. **Single Table-like Frame**: This refers to data returned in a standard tabular structure, where all values are organized into rows and columns, similar to what you'd get from a SQL query. - - **Example**: Any query against a SQL data source (e.g., PostgreSQL, MySQL) with the format set to Table. 2. **Dataplane: Time Series Format**: This format represents time series data with timestamps and associated values. It is typically returned from monitoring data sources. - - **Example**: Prometheus or Loki Range Queries (queries that return a set of values over time). 3. **Dataplane: Numeric Long Format**: diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md index 7782acad459..033115c665c 100644 --- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @@ -88,6 +88,8 @@ Transformations are a powerful way to manipulate data returned by a query before - Perform mathematical operations across queries - Use the output of one transformation as the input to another transformation +{{< docs/learning-journeys title="Transform data in a Grafana Cloud dashboard" url="https://grafana.com/docs/learning-journeys/data-transformation/" >}} + For users that rely on multiple views of the same dataset, transformations offer an efficient method of creating and maintaining numerous dashboards. You can also use the output of one transformation as the input to another transformation, which results in a performance gain. @@ -645,7 +647,7 @@ After choosing which field you want to group your data by, you can add various c | server 2 | 88.6 | | server 3 | 59.6 | -If you had added the count stat to the group by, there would be an extra column showing that the count of each server from above was 3. +If you had added the count stat to the group by transformation, there would be an extra column showing that the count of each server from above was 3. | Server ID | CPU Temperature (mean) | Server ID (count) | | --------- | ---------------------- | ----------------- | diff --git a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md index 44d0922b454..33b2bce6bfc 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md @@ -130,7 +130,6 @@ Set the mode of the gradient fill. Fill gradient is based on the line color. To - **Opacity** - Transparency of the gradient is calculated based on the values on the y-axis. Opacity of the fill is increasing with the values on the Y-axis. - **Hue** - Gradient color is generated based on the hue of the line color. - **Scheme** - The bar receives a gradient color defined by the **Standard options > Color scheme** selection. - - **From thresholds** - If the **Color scheme** selection is **From thresholds (by value)**, then each bar is the color of the defined threshold. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-colors-by-thresholds-v11.3.png" alt="Color scheme From thresholds" caption="Color scheme: From thresholds" >}} diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index cef103b2424..4be10911af6 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -470,7 +470,6 @@ You can style the selected connection using the following options: - **Size** - Control the size of the connection by entering a number in the **Value** field. - **Radius** - Add curve to the connection by entering a value to represent the degree. - **Arrow Direction** - Control the appearance of the arrow head. Choose from: - - **Forward** - The arrow head points in the direction in which the connection was drawn. - **Reverse** - The arrow head points in the opposite direction of which the connection was drawn. - **Both** - Adds arrow heads to both ends of the connection. diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index eb47c849ca1..707acfababc 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -59,6 +59,11 @@ refs: destination: /docs/grafana//panels-visualizations/query-transform-data/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/ + graph-styles: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/visualizations/time-series/#graph-styles-options + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/time-series/#graph-styles-options --- # Table @@ -155,7 +160,6 @@ To filter column values, follow these steps: 1. Click the checkbox next to the values that you want to display or click **Select all**. 1. Enter text in the search field at the top to show those values in the display so that you can select them rather than scroll to find them. 1. Choose from several operators to display column values: - - **Contains** - Matches a regex pattern (operator by default). - **Expression** - Evaluates a boolean expression. The character `$` represents the column value in the expression (for example, "$ >= 10 && $ <= 12"). - The typical comparison operators: `=`, `!=`, `<`, `<=`, `>`, `>=`. @@ -217,11 +221,8 @@ After you activate the table footer, make selections for the following options: ### Cell options Cell options allow you to control how data is displayed in a table. -The options are: - -- [Cell type](#cell-type) - Control the default cell display settings. -- [Wrap text](#wrap-text) - Wrap text in the cell that contains the longest content in your table. -- [Cell value inspect](#cell-value-inspect) - Enables value inspection from table cells. +The options are differ based on the cell type that you select and are outlined within the descriptions of each cell type. +The following table provides short descriptions for each cell type and links to a longer description and the cell type options. #### Cell type @@ -231,50 +232,88 @@ Additional configuration is available for some cell types. If you want to apply a cell type to only some fields instead of all fields, you can do so using the **Cell options > Cell type** field override. -| Cell type | Description | -| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Auto | Automatically displays values with sensible defaults applied. | -| [Sparkline](#sparkline) | Shows values rendered as a sparkline. | -| [Colored text](#colored-text) | If thresholds are set, then the field text is displayed in the appropriate threshold color. | -| [Colored background](#colored-background) | If thresholds are set, then the field background is displayed in the appropriate threshold color. | -| [Gauge](#gauge) | Cells can be displayed as a graphical gauge, with several different presentation types. You can set the [Gauge display mode](#gauge-display-mode) and the [Value display](#value-display) options. | + +| Cell type | Description | +| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| [Auto](#auto) | A basic text and number cell. | +| [Sparkline](#sparkline) | Shows values rendered as a sparkline. | +| [Colored text](#colored-text) | If thresholds, value mappings, or color schemes are set, then the cell text is displayed in the appropriate color. | +| [Colored background](#colored-background) | If thresholds, value mappings, or color schemes are set, then the cell background is displayed in the appropriate color. | +| [Gauge](#gauge) | Values are displayed as a horizontal bar gauge. You can set the [Gauge display mode](#gauge-display-mode) and the [Value display](#value-display) options. | | Data links | If you've configured data links, when the cell type is **Auto**, the cell text becomes clickable. If you change the cell type to **Data links**, the cell text reflects the titles of the configured data links. To control the application of data link text more granularly, use a **Cell option > Cell type > Data links** field override. | -| [JSON View](#json-view) | Shows values formatted as code. | -| [Image](#image) | If the field value is an image URL or a base64 encoded image, the table displays the image. | -| [Actions](#actions) | The cell displays a button that triggers a basic, unauthenticated API call when clicked. | +| [JSON View](#json-view) | Shows values formatted as code. | +| [Image](#image) | Displays an image when the value is a URL or a base64 encoded image. | +| [Actions](#actions) | The cell displays a button that triggers a basic, unauthenticated API call when clicked. | + -##### Sparkline +#### Auto + +This is a basic text and number cell. + +It has the following cell options: + +{{< docs/shared lookup="visualizations/cell-options.md" source="grafana" version="" >}} + +#### Sparkline This cell type shows values rendered as a sparkline. To show sparklines on data with multiple time series, use the [Time series to table transformation](ref:time-series-to-table-transformation) to process it into a format the table can show. ![Table using sparkline cell type](/media/docs/grafana/panels-visualizations/screenshot-table-as-sparkline-v11.3.png) -You can customize sparklines with many of the same options as the [time series visualization](ref:time-series-panel) including line style and width, fill opacity, gradient mode, and more. -You can also change the color of the sparkline by updating the [color scheme](ref:color-scheme) in the **Standard options** section of the panel configuration. +The sparkline cell type options are described in the following table. +For more detailed information about all of the sparkline styling options (except **Hide value**), refer to the [time series graph styles documentation](ref:graph-styles). -##### Colored text + +| Option | Description | +| ------------------- | --------------------------------------------------------------------------------------------- | +| Hide value | Toggle the switch on or off to display or hide the cell value on the sparkline. | +| Style | Choose whether to display your time-series data as **Lines**, **Bars**, or **Points**. You can use overrides to combine multiple styles in the same graph. | +| Line interpolation | How the graph interpolates the series line. Choose from:
  • **Linear** - Points are joined by straight lines.
  • **Smooth** - Points are joined by curved lines that smooths transitions between points.
  • **Step before** - The line is displayed as steps between points. Points are rendered at the end of the step.
  • **Step after** - The line is displayed as steps between points. Points are rendered at the beginning of the step.
| +| Line width | The thickness of the series lines or the outline for bars using the **Line width** slider. | +| Fill opacity | The series area fill color using the **Fill opacity** slider. | +| Gradient mode | Gradient mode controls the gradient fill, which is based on the series color. Gradient appearance is influenced by the **Fill opacity** setting. To change the color, use the standard color scheme field option. For more information, refer to [Color scheme](ref:color-scheme). Choose from:
  • **None** - No gradient fill. This is the default setting.
  • **Opacity** - An opacity gradient where the opacity of the fill increases as y-axis values increase.
  • **Hue** - A subtle gradient that's based on the hue of the series color.
| +| Line style | Choose from:
  • **Solid**
  • **Dash** - Select the length and gap for the line dashes. Default dash spacing is 10, 10.
  • **Dots** - Select the gap for the dot spacing. Default dot spacing is 0, 10.
| +| Connect null values | How null values, which are gaps in the data, appear on the graph. Null values can be connected to form a continuous line or set to a threshold above which gaps in the data are no longer connected. Choose from:
  • **Never** - Time series data points with gaps in the data are never connected.
  • **Always** - Time series data points with gaps in the data are always connected.
  • **Threshold** - Specify a threshold above which gaps in the data are no longer connected. This can be useful when the connected gaps in the data are of a known size or within a known range, and gaps outside this range should no longer be connected.
| +| Show points | Whether to show data points to lines or bars. Choose from:
  • **Auto** - Grafana determines a point's visibility based on the density of the data. If the density is low, then points appear.
  • **Always** - Show the points regardless of how dense the dataset is.
  • **Never** - Don't show points.
| +| Point size | Set the size of the points, from 1 to 40 pixels in diameter. | +| Bar alignment | Set the position of the bar relative to a data point. | + -If thresholds are set, with this cell type, the field text is displayed in the appropriate threshold color. +#### Colored text + +If thresholds, value mappings, or color schemes are set, the cell text is displayed in the appropriate color. ![Table with colored text cell type](/media/docs/grafana/panels-visualizations/screenshot-table-colored-text-v11.3-2.png) -{{< admonition type="note" >}} -This is an experimental feature. -{{< /admonition >}} +The colored text cell type has the following options: -##### Colored background +{{< docs/shared lookup="visualizations/cell-options.md" source="grafana" version="" >}} -If thresholds are set, with this cell type, the field background is displayed in the appropriate threshold color. +#### Colored background + +If thresholds, value mappings, or color schemes are set, the cell background is displayed in the appropriate color. ![Table with colored background cell type](/media/docs/grafana/panels-visualizations/screenshot-table-colored-bkgrnd-v11.3-2.png) -- **Background display mode** - Choose between **Basic** and **Gradient**. -- **Apply to entire row** - Toggle the switch on to apply the background color that's configured for the cell to the whole row. +You can also set background cell color by row: ![Table with background cell color applied to row](/media/docs/grafana/panels-visualizations/screenshot-table-colored-row-v11.3.png) -##### Gauge +The colored background cell type has the following options: + + +| Option | Description | +| ------ | ----------- | +| Background display mode | Choose between **Basic** and **Gradient**. | +| Apply to entire row | Toggle the switch on to apply the background color that's configured for the cell to the whole row. | +| Wrap text |

Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch.

Text wrapping is in [public preview](https://grafana.com/docs/release-life-cycle/#public-preview), however, it’s available to use by default.

| +| Cell value inspect |

Enables value inspection from table cells. When the switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer which contains two tabs: **Plain text** and **Code editor**.

Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. However, you can switch back and forth between tabs.

| + + + + +#### Gauge With this cell type, cells can be displayed as a graphical gauge, with several different presentation types controlled by the [gauge display mode](#gauge-display-mode) and the [value display](#value-display). @@ -283,53 +322,58 @@ The maximum and minimum values of the gauges are configured automatically from t If you don't want the max/min values to be pulled from the whole dataset, you can configure them for each column using [field overrides](#field-overrides). {{< /admonition >}} -###### Gauge display mode +##### Gauge display mode You can set three gauge display modes. - | Option | Description | | ------ | ----------- | | Basic | Shows a simple gauge with the threshold levels defining the color of gauge. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-gauge-mode-basic-v11.3.png" alt="Table cell with basic gauge mode" >}} | | Gradient | The threshold levels define a gradient. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-gauge-mode-gradient-v11.3.png" alt="Table cell with gradient gauge mode" >}} | | Retro LCD | The gauge is split up in small cells that are lit or unlit. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-gauge-mode-retro-v11.3.png" alt="Table cell with retro LCD gauge mode" >}} | - -###### Value display +##### Value display Labels displayed alongside of the gauges can be set to be colored by value, match the theme text color, or be hidden. - | Option | Description | | ------ | ----------- | | Value color | Labels are colored by value. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-labels-value-color-v11.3.png" alt="Table with labels in value color" >}} | | Text color | Labels match the theme text color. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-labels-text-color-v11.3.png" alt="Table with labels in theme color" >}} | | Hidden | Labels are hidden. {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-labels-hidden-v11.3.png" alt="Table with labels hidden" >}} | - -##### JSON View +#### JSON View This cell type shows values formatted as code. If a value is an object the JSON view allowing browsing the JSON object will appear on hover. {{< figure src="/static/img/docs/tables/json-view.png" max-width="350px" alt="JSON view" class="docs-image--no-shadow" >}} -##### Image +For the JSON view cell type, you can set enable **Cell value inspect**. +This enables value inspection from table cells. +When the switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer which contains two tabs: **Plain text** and **Code editor**. + +Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. +However, you can switch back and forth between tabs + +#### Image If you have a field value that is an image URL or a base64 encoded image, this cell type displays it as an image. ![Table with image cell type](/media/docs/grafana/panels-visualizations/screenshot-table-cell-image-v11.3.png) -Set the following options: +It has the following options: -- **Alt text** - Set the alternative text of an image. The text will be available for screen readers and in cases when images can't be loaded. -- **Title text** - Set the text that's displayed when the image is hovered over with a cursor. +| Option | Description | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Alt text | Set the alternative text of an image. The text will be available for screen readers and in cases when images can't be loaded. | +| Title text | Set the text that's displayed when the image is hovered over with a cursor. | -##### Actions +#### Actions The cell displays a button that triggers a basic, unauthenticated API call when clicked. Configure the API call with the following options: @@ -343,34 +387,8 @@ Configure the API call with the following options: | Query parameters | Enter as many **Key**, **Value** pairs as you need. | | Header parameters | Enter as many **Key**, **Value** pairs as you need. | | Payload | Enter the body of the API call. | - -#### Wrap text - -{{< admonition type="note" >}} -Text wrapping is in [public preview](https://grafana.com/docs/release-life-cycle/#public-preview), however, it’s available to use by default. -We’d love hear from you about how this new feature is working. To provide feedback, you can open an issue in the [Grafana GitHub repository](https://github.com/grafana/grafana). -{{< /admonition >}} - -Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. - -To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch. - -This option is available for the following cell types: **Auto**, **Colored text**, and **Colored background**. - -#### Cell value inspect - -Enables value inspection from table cells. When the **Cell inspect value** switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer. - -The **Inspect value** drawer has two tabs, **Plain text** and **Code editor**. -Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. -However, you can switch back and forth between tabs. - -This option is available for the following cell types: **Auto**, **Colored text**, **Colored background**, and **JSON View**. - -If you want to apply this setting to only some fields instead of all fields, you can do so using the **Cell options > Cell value inspect** field override. - ### Standard options {{< docs/shared lookup="visualizations/standard-options.md" source="grafana" version="" >}} diff --git a/docs/sources/panels-visualizations/visualizations/traces/index.md b/docs/sources/panels-visualizations/visualizations/traces/index.md index a82d0be2662..abf7fc1ddc3 100644 --- a/docs/sources/panels-visualizations/visualizations/traces/index.md +++ b/docs/sources/panels-visualizations/visualizations/traces/index.md @@ -91,7 +91,6 @@ This procedure uses dashboard variables and templates to allow you to enter trac 1. From your Grafana stack, create a new dashboard or go to an existing dashboard where you'd like to add traces visualizations. 1. Do one of the following: - - New dashboard - Click **+ Add visualization**. - Existing dashboard - Click **Edit** in the top-right corner and then select **Visualization** in the **Add** drop-down. diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 1ce20139b48..a79ccc33e70 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2655,8 +2655,6 @@ If `true`, propagate the tracing context to the plugin backend and enable tracin Load an external version of a core plugin if it has been installed. -Experimental. Requires the feature toggle `externalCorePlugins` to be enabled. -
### `[plugin.grafana-image-renderer]` diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index a471a92f08c..cc448ec4ba3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -35,14 +35,12 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes | | `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes | | `unifiedRequestLog` | Writes error logs to the request logger | Yes | -| `pluginsDetailsRightPanel` | Enables right panel for the plugins details page | Yes | | `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes | | `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes | | `transformationsRedesign` | Enables the transformations redesign | Yes | | `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes | | `dashgpt` | Enable AI powered features in dashboards | Yes | -| `externalCorePlugins` | Allow core plugins to be loaded as external | Yes | | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | | `formatString` | Enable format string transformer | Yes | | `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes | @@ -108,6 +106,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource | | `alertRuleRestore` | Enables the alert rule restore feature | | `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source | +| `localeFormatPreference` | Specifies the locale so the correct format for numbers and dates can be shown | | `logsPanelControls` | Enables a control component for the logs panel in Explore | ## Development feature toggles diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md index f381c212abb..3d1c4a48a6b 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md @@ -120,9 +120,7 @@ In this example we use Apache as a reverse proxy in front of Grafana. Apache han - The first four lines of the virtualhost configuration are standard, so we won’t go into detail on what they do. - We use a **\** configuration block for applying our authentication rules to every proxied request. These rules include requiring basic authentication where user:password credentials are stored in the **/etc/apache2/grafana_htpasswd** file. This file can be created with the `htpasswd` command. - - The next part of the configuration is the tricky part. We use Apache’s rewrite engine to create our **X-WEBAUTH-USER header**, populated with the authenticated user. - - **RewriteRule .\* - [E=PROXY_USER:%{LA-U:REMOTE_USER}, NS]**: This line is a little bit of magic. What it does, is for every request use the rewriteEngines look-ahead (LA-U) feature to determine what the REMOTE_USER variable would be set to after processing the request. Then assign the result to the variable PROXY_USER. This is necessary as the REMOTE_USER variable is not available to the RequestHeader function. - **RequestHeader set X-WEBAUTH-USER “%{PROXY_USER}e”**: With the authenticated username now stored in the PROXY_USER variable, we create a new HTTP request header that will be sent to our backend Grafana containing the username. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md index a4ce61cd31a..69e6e1415f0 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md @@ -42,16 +42,12 @@ To enable the Azure AD/Entra ID OAuth, register your application with Entra ID. 1. Note the **Application ID**. This is the OAuth client ID. 1. Click **Endpoints** from the top menu. - - Note the **OAuth 2.0 authorization endpoint (v2)** URL. This is the authorization URL. - Note the **OAuth 2.0 token endpoint (v2)**. This is the token URL. 1. Click **Certificates & secrets** in the side menu, then add a new entry under the supported client authentication option you want to use. The following are the supported client authentication options with their respective configuration steps. - - **Client secrets** - 1. Add a new entry under **Client secrets** with the following configuration. - - Description: Grafana OAuth 2.0 - Expires: Select an expiration period @@ -60,16 +56,12 @@ To enable the Azure AD/Entra ID OAuth, register your application with Entra ID. {{< admonition type="note" >}} Make sure that you copy the string in the **Value** field, rather than the one in the **Secret ID** field. {{< /admonition >}} - 1. You must have set `client_authentication` under `[auth.azuread]` to `client_secret_post` in the Grafana server configuration for this to work. - **Federated credentials** - - **_Managed Identity_** - 1. Refer to [Configure an application to trust a managed identity (preview)](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity?tabs=microsoft-entra-admin-center) for a complete guide on setting up a managed identity as a federated credential. Add a new entry under Federated credentials with the following configuration. - - Federated credential scenario: Select **Other issuer**. - Issuer: The OAuth 2.0 / OIDC issuer URL of the Microsoft Entra ID authority. For example: `https://login.microsoftonline.com/{tenantID}/v2.0`. - Subject identifier: The Object (Principal) ID GUID of the Managed Identity. @@ -88,10 +80,8 @@ To enable the Azure AD/Entra ID OAuth, register your application with Entra ID. {{< /admonition >}} - **_Workload Identity (K8s/AKS)_** - 1. Refer to [Federated identity credential for an Azure AD application](https://azure.github.io/azure-workload-identity/docs/topics/federated-identity-credential.html#azure-portal-ui) for a complete guide on setting up a federated credential for workload identity. Add a new entry under Federated credentials with the following configuration. - - Federated credential scenario: Select **Kubernetes accessing Azure resources**. - [Cluster issuer URL](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer#get-the-oidc-issuer-url): The OIDC issuer URL that your cluster is integrated with. For example: `https://{region}.oic.prod-aks.azure.com/{tenant_id}/{uuid}`. - Namespace: Namespace of your Grafana deployment. For example: `grafana`. @@ -141,7 +131,6 @@ This section describes setting up basic application roles for Grafana within the 1. Click **App roles** and then **Create app role**. 1. Define a role corresponding to each Grafana role: Viewer, Editor, and Admin. - 1. Choose a **Display name** for the role. For example, "Grafana Editor". 1. Set the **Allowed member types** to **Users/Groups**. @@ -271,6 +260,10 @@ resource "grafana_sso_settings" "azuread_sso_settings" { allow_assign_grafana_admin = false skip_org_role_sync = false use_pkce = true + custom = { + domain_hint = "contoso.com" + force_use_graph_api = "true" + } } } ``` diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md index 9e10faee645..9ebe70e6e55 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md @@ -450,7 +450,6 @@ Support for the Auth0 "audience" feature is not currently available in Grafana. To set up Generic OAuth authentication with Auth0, follow these steps: 1. Create an Auth0 application using the following parameters: - - Name: Grafana - Type: Regular Web Application @@ -485,7 +484,6 @@ To set up Generic OAuth authentication with Bitbucket, follow these steps: 1. Navigate to **Settings > Workspace setting > OAuth consumers** in BitBucket. 1. Create an application by selecting **Add consumer** and using the following parameters: - - Allowed Callback URLs: `https:///login/generic_oauth` 1. Click **Save**. @@ -518,7 +516,6 @@ By default, a refresh token is included in the response for the **Authorization To set up Generic OAuth authentication with OneLogin, follow these steps: 1. Create a new Custom Connector in OneLogin with the following settings: - - Name: Grafana - Sign On Method: OpenID Connect - Redirect URI: `https:///login/generic_oauth` @@ -526,7 +523,6 @@ To set up Generic OAuth authentication with OneLogin, follow these steps: - Login URL: `https:///login/generic_oauth` 1. Add an app to the Grafana Connector: - - Display Name: Grafana 1. Update the `[auth.generic_oauth]` section of the Grafana configuration file using the client ID and client secret from the **SSO** tab of the app details page: diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md index 3ab7695554c..22363e59b54 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md @@ -88,7 +88,6 @@ Ensure that you have access to the [Grafana configuration file](../../../configu To configure GitLab authentication with Grafana, follow these steps: 1. Create an OAuth application in GitLab. - 1. Set the redirect URI to `http://:/login/gitlab`. Ensure that the Redirect URI is the complete HTTP address that you use to access Grafana via your browser, but with the appended path of `/login/gitlab`. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap-ui/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap-ui/_index.md index 7deaab7f849..ea8ec60d0f5 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap-ui/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap-ui/_index.md @@ -87,7 +87,6 @@ Map LDAP groups to Grafana roles. 1. **Manage group mappings**: When managing group mappings, the following fields are available. To add a new group mapping, click the **Add group mapping** button. - 1. **Add a group DN mapping**: The name of the key used to extract the ID token. 1. **Add an organization role mapping**: Select the Basic Role mapped to this group. 1. **Add the organization ID membership mapping**: Map the group to an organization ID. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap/index.md index 872aab03a97..fac378a498c 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap/index.md @@ -134,6 +134,10 @@ email = "email" Whenever you modify the ldap.toml file, you must restart Grafana in order for the change(s) to take effect. {{< /admonition >}} +### Using the Grafana user interface + +You can configure LDAP using the Grafana user interface by navigating to **Administration > Authentication > LDAP**. Please refer to the [LDAP user interface](../ldap-ui/) documentation for more information. + ### Using environment variables You can interpolate variables in the TOML configuration from environment variables. For instance, you could externalize your `bind_password` that way: @@ -142,28 +146,6 @@ You can interpolate variables in the TOML configuration from environment variabl bind_password = "${LDAP_ADMIN_PASSWORD}" ``` -## LDAP debug view - -Grafana has an LDAP debug view built-in which allows you to test your LDAP configuration directly within Grafana. Only Grafana admins can use the LDAP debug view. - -Within this view, you'll be able to see which LDAP servers are currently reachable and test your current configuration. - -{{< figure src="/static/img/docs/ldap_debug.png" class="docs-image--no-shadow" max-width="600px" alt="LDAP testing" >}} - -To use the debug view, complete the following steps: - -1. Type the username of a user that exists within any of your LDAP server(s) -1. Then, press "Run" -1. If the user is found within any of your LDAP instances, the mapping information is displayed. - -Note that this does not work if you are using the single bind configuration outlined below. - -{{< figure src="/static/img/docs/ldap_debug_mapping_testing.png" class="docs-image--no-shadow" max-width="600px" alt="LDAP mapping displayed" >}} - -[Grafana Enterprise](../../../../introduction/grafana-enterprise/) users with [enhanced LDAP integration](../enhanced-ldap/) enabled can also see sync status in the debug view. This requires the `ldap.status:read` permission. - -{{< figure src="/static/img/docs/ldap_sync_debug.png" class="docs-image--no-shadow" max-width="600px" alt="LDAP sync status" >}} - ### Bind and bind password By default the configuration expects you to specify a bind DN and bind password. This should be a read only user that can perform LDAP searches. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md index 3a90f19b371..8166760890f 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md @@ -30,7 +30,6 @@ To follow this guide, ensure you have permissions in your Okta workspace to crea 1. For **Sign-in method**, select **OIDC - OpenID Connect**. 1. For **Application type**, select **Web Application** and click **Next**. 1. Configure **New Web App Integration Operations**: - - **App integration name**: Choose a name for the app. - **Logo (optional)**: Add a logo. - **Grant type**: Select **Authorization Code** and **Refresh Token**. @@ -54,7 +53,6 @@ To follow this guide, ensure you have permissions in your Okta workspace to crea 1. In the **Okta Admin Console**, select **Directory > Profile Editor**. 1. Select the Okta Application Profile you created previously (the default name for this is ` User`). 1. Select **Add Attribute** and fill in the following fields: - - **Data Type**: string - **Display Name**: Meaningful name. For example, `Grafana Role`. - **Variable Name**: Meaningful name. For example, `grafana_role`. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/_index.md index 6dd7f45042d..40cfc2441d8 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/_index.md @@ -50,7 +50,6 @@ Configuration in the API takes precedence over the configuration in the Grafana Grafana supports the following SAML 2.0 bindings: - From the Service Provider (SP) to the Identity Provider (IdP): - - `HTTP-POST` binding - `HTTP-Redirect` binding @@ -181,12 +180,10 @@ By default, new Grafana users using SAML authentication will have an account cre If you are also using SCIM provisioning for this Grafana application in Azure AD, it's crucial to align the user identifiers between SAML and SCIM for seamless operation. The unique identifier that links the SAML user to the SCIM provisioned user is determined by the `assertion_attribute_external_uid` setting in the Grafana SAML configuration. This `assertion_attribute_external_uid` should correspond to the `externalId` used in SCIM provisioning (typically set to the Azure AD `user.objectid`). 1. **Ensure Consistent Identifier in SAML Assertion:** - - The unique identifier from Azure AD (typically `user.objectid`) that you mapped to the `externalId` attribute in Grafana in your SCIM provisioning setup **must also be sent as a claim in the SAML assertion.** For more details on SCIM, refer to the [SCIM provisioning documentation](/docs/grafana//setup-grafana/configure-security/configure-scim-provisioning/). - In the Azure AD Enterprise Application, under **Single sign-on** > **Attributes & Claims**, ensure you add a claim that provides this identifier. For example, you might add a claim named `UserID` (or similar, like `externalId`) that sources its value from `user.objectid`. 2. **Configure Grafana SAML Settings for SCIM:** - - In the `[auth.saml]` section of your Grafana configuration, set `assertion_attribute_external_uid` to the name of the SAML claim you configured in the previous step (e.g., `userUID` or the full URI like `http://schemas.microsoft.com/identity/claims/objectidentifier` if that's how Azure AD sends it). - The `assertion_attribute_login` setting should still be configured to map to the attribute your users will log in with (e.g., `userPrincipalName`, `mail`). diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-okta/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-okta/_index.md index 123a1298385..bb16de2b0ee 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-okta/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-okta/_index.md @@ -28,7 +28,6 @@ Grafana supports user authentication through Okta, which is useful when you want 1. Click **Create**. 1. On the **General Settings** tab, enter a name for your Grafana integration. You can also upload a logo. 1. On the **Configure SAML** tab, enter the SAML information related to your Grafana instance: - - In the **Single sign on URL** field, use the `/saml/acs` endpoint URL of your Grafana instance, for example, `https://grafana.example.com/saml/acs`. - In the **Audience URI (SP Entity ID)** field, use the `/saml/metadata` endpoint URL, by default it is the `/saml/metadata` endpoint of your Grafana instance (for example `https://example.grafana.com/saml/metadata`). This could be configured differently, but the value here must match the `entity_id` setting of the SAML settings of Grafana. - Leave the default values for **Name ID format** and **Application username**. diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md index 7836a46df9f..52d2c84fbfd 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md @@ -64,7 +64,6 @@ Sign in to Grafana and navigate to **Administration > Authentication > Configure ### 2. Sign Requests Section 1. In the **Sign requests** field, specify whether you want the outgoing requests to be signed, and, if so, then: - 1. Provide a certificate and a private key that will be used by the service provider (Grafana) and the SAML IdP. Use the [PKCS #8](https://en.wikipedia.org/wiki/PKCS_8) format to issue the private key. diff --git a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-aws-kms/index.md b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-aws-kms/index.md index d081a085cb6..3b83e927616 100644 --- a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-aws-kms/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-aws-kms/index.md @@ -33,7 +33,6 @@ You can use an encryption key from AWS Key Management Service to encrypt secrets

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.awskms.]`, where `` is any name that uniquely identifies this key among other provider keys.

b. Fill in the section with the following values:
- - `key_id`: a reference to a key stored in the KMS. This can be a key ID, a key Amazon Resource Name (ARN), an alias name, or an alias ARN. If you are using an alias, use the prefix `alias/`. To specify a KMS key in a different AWS account, use its ARN or alias. For more information about how to retrieve a key ID from AWS, refer to [Finding the key ID and key ARN](https://docs.aws.amazon.com/kms/latest/developerguide/find-cmk-id-arn.html).
| `key_id` option | Example value | | --- | --- | diff --git a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-azure-key-vault/index.md b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-azure-key-vault/index.md index a13d6f7b41e..2b1a959c75b 100644 --- a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-azure-key-vault/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-azure-key-vault/index.md @@ -35,7 +35,6 @@ You can use an encryption key from Azure Key Vault to encrypt secrets in the Gra

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.azurekv.]`, where `` is any name that uniquely identifies this key among other provider keys.

b. Fill in the section with the following values:
- - `tenant_id`: the **Directory ID** (tenant) from the application that you registered. - `client_id`: the **Application ID** (client) from the application that you registered. - `client_secret`: the VALUE of the secret that you generated in your app. (Don't use the Secret ID). diff --git a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-google-cloud-kms/index.md b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-google-cloud-kms/index.md index 3d90697e310..4691ab90674 100644 --- a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-google-cloud-kms/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-google-cloud-kms/index.md @@ -33,7 +33,6 @@ You can use an encryption key from Google Cloud Key Management Service to encryp

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.azurekv.]`, where `` is any name that uniquely identifies this key among other provider keys.

b. Fill in the section with the following values:
- - `key_id`: encryption key ID, refer to [Getting the ID for a Key](https://cloud.google.com/kms/docs/getting-resource-ids#getting_the_id_for_a_key_and_version). - `credentials_file`: full path to service account key JSON file on your computer. diff --git a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-hashicorp-key-vault/index.md b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-hashicorp-key-vault/index.md index e15a46679b0..feee0cda4fb 100644 --- a/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-hashicorp-key-vault/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-database-encryption/encrypt-secrets-using-hashicorp-key-vault/index.md @@ -31,7 +31,6 @@ You can use an encryption key from Hashicorp Vault to encrypt secrets in the Gra

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.hashicorpvault.]`, where `` is any name that uniquely identifies this key among other provider keys.

b. Fill in the section with the following values:
- - `token`: a periodic service token used to authenticate within Hashicorp Vault. - `url`: URL of the Hashicorp Vault server. - `transit_engine_path`: mount point of the transit engine. diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md index afc7cb26c40..d3c2496f75b 100644 --- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md @@ -50,7 +50,7 @@ Always verify that your SAML identity provider is configured to send a stable, u ## Benefits {{< admonition type="note" >}} -SCIM provisioning only works SAML authentication. +SCIM provisioning only works with SAML authentication. Other authentication methods aren't supported. {{< /admonition >}} @@ -67,27 +67,19 @@ When you enable SCIM in Grafana, the following requirements and restrictions app 1. **Use the same identity provider for user provisioning and for authentication flow**: You must use the same identity provider for both authentication and user provisioning. -2. **Authentication restrictions**: - - - Users attempting to log in through other methods (LDAP, OAuth) will be blocked - - By default, users who are not provisioned through SCIM cannot access Grafana - -3. **Security restriction**: When using SAML, the login authentication flow requires the SAML assertion exchange between the Identity Provider and Grafana to include the `userUID` SAML assertion with the user's unique identifier at the Identity Provider. - +2. **Security restriction**: When using SAML, the login authentication flow requires the SAML assertion exchange between the Identity Provider and Grafana to include the `userUID` SAML assertion with the user's unique identifier at the Identity Provider. - Configure `userUID` SAML assertion in [Azure AD](/docs/grafana//setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-azuread/#configure-saml-assertions-when-using-scim-provisioning) - Configure `userUID` SAML assertion in [Okta](/docs/grafana//setup-grafana/configure-security/configure-authentication/saml/configure-saml-with-okta/#configure-saml-assertions-when-using-scim-provisioning) -4. **Exceptions**: Users with Basic Auth credentials and those using their Grafana Cloud accounts can still log in regardless of these restrictions. - ## Configure SCIM in Grafana The table below describes all SCIM configuration options. Like any other Grafana configuration, you can apply these options as [environment variables](/docs/grafana//setup-grafana/configure-grafana/#override-configuration-with-environment-variables). -| Setting | Required | Description | Default | -| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -| `user_sync_enabled` | Yes | Enable SCIM user provisioning. When enabled, Grafana will create, update, and deactivate users based on SCIM requests from your identity provider. | `false` | -| `group_sync_enabled` | No | Enable SCIM group provisioning. When enabled, Grafana will create, update, and delete teams based on SCIM requests from your identity provider. Cannot be enabled if Team Sync is enabled. | `false` | -| `allow_non_provisioned_users` | No | Allow non SCIM provisioned users to sign in to Grafana. | `false` | +| Setting | Required | Description | Default | +| ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `user_sync_enabled` | Yes | Enable SCIM user provisioning. When enabled, Grafana will create, update, and deactivate users based on SCIM requests from your identity provider. | `false` | +| `group_sync_enabled` | No | Enable SCIM group provisioning. When enabled, Grafana will create, update, and delete teams based on SCIM requests from your identity provider. Cannot be enabled if Team Sync is enabled. | `false` | +| `reject_non_provisioned_users` | No | When enabled, prevents non-SCIM provisioned users from signing in. Cloud Portal users can always sign in regardless of this setting. | `false` | {{< admonition type="warning" >}} **Team Sync Compatibility**: diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md index fb031c15c10..1554484f45b 100644 --- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md @@ -112,12 +112,13 @@ After setting the Tenant URL and Secret Token, navigate to the **Mappings** sect Configure the following required attributes: -| Azure AD Attribute | Grafana Attribute | -| ------------------- | ------------------------------ | -| `userPrincipalName` | `userName` | -| `mail` | `emails[type eq "work"].value` | -| `displayName` | `displayName` | -| `objectId` | `externalId` | +| Azure AD Attribute | Grafana Attribute | +| ------------------------------------------------------------- | ------------------------------ | +| `userPrincipalName` | `userName` | +| `mail` | `emails[type eq "work"].value` | +| `displayName` | `displayName` | +| `objectId` | `externalId` | +| `Switch([IsSoftDeleted], , "False", "True", "True", "False")` | `active` | ### Enable provisioning diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md index 5fb60103ad6..2b3b0524884 100644 --- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md +++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md @@ -57,15 +57,13 @@ For detailed configuration steps specific to the identity provider, see: SCIM uses a specific process to establish and maintain user identity between the identity provider and Grafana: 1. **Initial user lookup:** - - The administrator configures SCIM at the Identity Provider, defining the **Unique identifier field** - The identity provider looks up each user in Grafana using this unique identifier field as a filter - The identity provider expects a single result from Grafana for each user 2. **Identity linking based on lookup results:** - - **If there's a single matching result:** The identity provider retrieves the user's unique ID at Grafana, saves it, confirms it can fetch the user's information, and updates the user's information in Grafana - - **If there are no matching results:** The identity provider attempts to create the user in Grafana. If successful, it retrieves and saves the user's unique ID for future operations. If there's a conflict with an existing user, the identity provider flags the error and Grafana logs the error message + - **If there are no matching results:** The identity provider attempts to create the user in Grafana. If successful, it retrieves and saves the user's unique ID for future operations. If a user with the same email address already exists in Grafana, the user is updated and will be managed by SCIM from that point forward. - The identity provider learns the relationship between the found Grafana user and the Grafana internal ID - The identity provider updates Grafana with the External ID - Grafana updates the authentication validations to expect this External ID @@ -81,10 +79,6 @@ This process ensures secure and consistent user identification across both syste ### Existing Grafana users -{{< admonition type="note" >}} -Existing users must be assigned to the Grafana app in the identity provider to maintain access once SCIM is enabled. -{{< /admonition >}} - For users who already exist in the Grafana instance: - SCIM establishes the relationship through the External ID matching process @@ -97,12 +91,10 @@ For users who already exist in the Grafana instance: To prevent conflicts and maintain consistent user management, disable or restrict other provisioning methods when implementing SCIM. This ensures that all new users are created through SCIM and prevents duplicate or conflicting user records. - SAML Just-in-Time (JIT) provisioning: - - Disable `allow_sign_up` in SAML settings to prevent automatic user creation - Existing JIT-provisioned users will continue to work but should be migrated to SCIM - Terraform or API provisioning: - - Stop creating new users through these methods - Existing users will continue to work but should be migrated to SCIM - Consider removing or archiving Terraform user creation resources @@ -141,31 +133,26 @@ The migration process uses the same [user identification mechanism](#how-scim-id ### Migration steps 1. **Prepare the identity provider:** - - Ensure all existing Grafana users have corresponding accounts in your IDP - Verify that the unique identifier field (e.g., email, username, or object ID) matches between systems - Configure SCIM application in your IDP but don't assign users yet 2. **Configure SCIM in Grafana:** - - Set up SCIM endpoint and authentication as described in [Configure SCIM in Grafana](../../configure-scim-provisioning#configure-scim-in-grafana) - Enable `user_sync_enabled = true` - Configure the unique identifier field to match your IDP setup {{< admonition type="note" >}} -When `user_sync_enabled = true`, non-provisioned users will be disallowed from logging in, except for `admin` and Grafana.com login. - -If you want to allow non-provisioned users to log in, enable the `[auth.scim][allow_non_provisioned_users]` option. +To restrict login access to only SCIM-provisioned users, enable the `[auth.scim][reject_non_provisioned_users]` option. Cloud Portal users can always sign in regardless of this setting. ```ini [auth.scim] -allow_non_provisioned_users = true +reject_non_provisioned_users = true ``` {{< /admonition >}} 3. **Test the matching mechanism:** - - Use the SCIM API to verify that existing users can be found using the unique identifier: ```bash @@ -176,7 +163,6 @@ allow_non_provisioned_users = true - This should return exactly one user record for each existing user 4. **Assign users in the IDP:** - - Begin assigning existing users to the Grafana application in your IDP - The SCIM identification process will automatically link existing Grafana users with their IDP identities - Monitor the process for any conflicts or errors @@ -290,19 +276,6 @@ Team membership maintenance: ### User provisioning issues -#### Error: "User already exists in Grafana" - -**Cause:** The unique identifier field is not working as expected, causing conflicts during user creation. - -**Solution:** Test the unique identifier field to ensure it returns a single, unique user: - -```bash -curl --location 'https://{$GRAFANA_URL}/apis/scim.grafana.app/v0alpha1/namespaces/{$STACK_ID}/Users?filter=userName eq "username@email.com"' \ ---header 'Authorization: Bearer glsa_xxxxxxxxxxxxxxxxxxxxxxxx' -``` - -The response should return exactly one user. If not, configure a different unique identifier field in your identity provider, or remove the duplicate users from Grafana. - #### Error: "invalid namespace" **Cause:** The SCIM endpoint URL is incorrectly formatted. diff --git a/docs/sources/setup-grafana/configure-security/configure-team-sync.md b/docs/sources/setup-grafana/configure-security/configure-team-sync.md index f99de6a6189..268e907908f 100644 --- a/docs/sources/setup-grafana/configure-security/configure-team-sync.md +++ b/docs/sources/setup-grafana/configure-security/configure-team-sync.md @@ -51,7 +51,6 @@ If you have already grouped some users into a team, then you can synchronize tha 1. Insert the value of the group you want to sync with. This becomes the Grafana `GroupID`. Examples: - - For LDAP, this is the LDAP distinguished name (DN) of LDAP group you want to synchronize with the team. - For Auth Proxy, this is the value we receive as part of the custom `Groups` header. diff --git a/docs/sources/setup-grafana/image-rendering/_index.md b/docs/sources/setup-grafana/image-rendering/_index.md index 43c45bf19e5..568f2d4e6a9 100644 --- a/docs/sources/setup-grafana/image-rendering/_index.md +++ b/docs/sources/setup-grafana/image-rendering/_index.md @@ -59,11 +59,16 @@ You can update your settings by using a configuration file, see [default.json](h You can volume mount your custom configuration file when starting the docker container: ```bash -docker run -d --name=renderer --network=host -v /some/path/config.json:/usr/src/app/config.json grafana/grafana-image-renderer:latest +docker run -d --name=renderer --network=host -v /some/path/config.json:/home/nonroot/config.json grafana/grafana-image-renderer:latest ``` You can see a docker-compose example using a custom configuration file [here](https://github.com/grafana/grafana-image-renderer/tree/master/devenv/docker/custom-config). +{{< admonition type="note" >}} +The configuration files were located in `/usr/src/app` up until v4.0.0 and later. +After this point, they are located in `/home/nonroot`. +{{< /admonition >}} + ### Security {{< admonition type="note" >}} diff --git a/docs/sources/setup-grafana/image-rendering/troubleshooting/index.md b/docs/sources/setup-grafana/image-rendering/troubleshooting/index.md index 3c33d01dbc3..61cd711bd50 100644 --- a/docs/sources/setup-grafana/image-rendering/troubleshooting/index.md +++ b/docs/sources/setup-grafana/image-rendering/troubleshooting/index.md @@ -61,49 +61,8 @@ ldd chrome-headless-shell/linux-132.0.6781.0/chrome-headless-shell-linux64/chrom ... ``` -**Ubuntu:** - -On Ubuntu 18.10 the following dependencies are required for the image rendering to function. - -```bash -libx11-6 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrender1 libxtst6 libglib2.0-0 libnss3 libcups2 libdbus-1-3 libxss1 libxrandr2 libgtk-3-0 libasound2 libxcb-dri3-0 libgbm1 libxshmfence1 -``` - -**Debian:** - -On Debian 9 (Stretch) the following dependencies are required for the image rendering to function. - -```bash -libx11 libcairo libcairo2 libxtst6 libxcomposite1 libx11-xcb1 libxcursor1 libxdamage1 libnss3 libcups libcups2 libxss libxss1 libxrandr2 libasound2 libatk1.0-0 libatk-bridge2.0-0 libpangocairo-1.0-0 libgtk-3-0 libgbm1 libxshmfence1 -``` - -On Debian 10 (Buster) the following dependencies are required for the image rendering to function. - -```bash -libxdamage1 libxext6 libxi6 libxtst6 libnss3 libcups2 libxss1 libxrandr2 libasound2 libatk1.0-0 libatk-bridge2.0-0 libpangocairo-1.0-0 libpango-1.0-0 libcairo2 libatspi2.0-0 libgtk3.0-cil libgdk3.0-cil libx11-xcb-dev libgbm1 libxshmfence1 -``` - -**Centos:** - -On a minimal CentOS 7 installation, the following dependencies are required for the image rendering to function: - -```bash -libXcomposite libXdamage libXtst cups libXScrnSaver pango atk adwaita-cursor-theme adwaita-icon-theme at at-spi2-atk at-spi2-core cairo-gobject colord-libs dconf desktop-file-utils ed emacs-filesystem gdk-pixbuf2 glib-networking gnutls gsettings-desktop-schemas gtk-update-icon-cache gtk3 hicolor-icon-theme jasper-libs json-glib libappindicator-gtk3 libdbusmenu libdbusmenu-gtk3 libepoxy liberation-fonts liberation-narrow-fonts liberation-sans-fonts liberation-serif-fonts libgusb libindicator-gtk3 libmodman libproxy libsoup libwayland-cursor libwayland-egl libxkbcommon m4 mailx nettle patch psmisc redhat-lsb-core redhat-lsb-submod-security rest spax time trousers xdg-utils xkeyboard-config alsa-lib -``` - -On a minimal CentOS 8 installation, the following dependencies are required for the image rendering to function: - -```bash -libXcomposite libXdamage libXtst cups libXScrnSaver pango atk adwaita-cursor-theme adwaita-icon-theme at at-spi2-atk at-spi2-core cairo-gobject colord-libs dconf desktop-file-utils ed emacs-filesystem gdk-pixbuf2 glib-networking gnutls gsettings-desktop-schemas gtk-update-icon-cache gtk3 hicolor-icon-theme jasper-libs json-glib libappindicator-gtk3 libdbusmenu libdbusmenu-gtk3 libepoxy liberation-fonts liberation-narrow-fonts liberation-sans-fonts liberation-serif-fonts libgusb libindicator-gtk3 libmodman libproxy libsoup libwayland-cursor libwayland-egl libxkbcommon m4 mailx nettle patch psmisc redhat-lsb-core redhat-lsb-submod-security rest spax time trousers xdg-utils xkeyboard-config alsa-lib libX11-xcb -``` - -**RHEL:** - -On a minimal RHEL 8 installation, the following dependencies are required for the image rendering to function: - -```bash -linux-vdso.so.1 libdl.so.2 libpthread.so.0 libgobject-2.0.so.0 libglib-2.0.so.0 libnss3.so libnssutil3.so libsmime3.so libnspr4.so libatk-1.0.so.0 libatk-bridge-2.0.so.0 libcups.so.2 libgio-2.0.so.0 libdrm.so.2 libdbus-1.so.3 libexpat.so.1 libxcb.so.1 libxkbcommon.so.0 libm.so.6 libX11.so.6 libXcomposite.so.1 libXdamage.so.1 libXext.so.6 libXfixes.so.3 libXrandr.so.2 libgbm.so.1 libpango-1.0.so.0 libcairo.so.2 libasound.so.2 libatspi.so.0 libgcc_s.so.1 libc.so.6 /lib64/ld-linux-x86-64.so.2 libgnutls.so.30 libpcre.so.1 libffi.so.6 libplc4.so libplds4.so librt.so.1 libgmodule-2.0.so.0 libgssapi_krb5.so.2 libkrb5.so.3 libk5crypto.so.3 libcom_err.so.2 libavahi-common.so.3 libavahi-client.so.3 libcrypt.so.1 libz.so.1 libselinux.so.1 libresolv.so.2 libmount.so.1 libsystemd.so.0 libXau.so.6 libXrender.so.1 libthai.so.0 libfribidi.so.0 libpixman-1.so.0 libfontconfig.so.1 libpng16.so.16 libxcb-render.so.0 libidn2.so.0 libunistring.so.2 libtasn1.so.6 libnettle.so.6 libhogweed.so.4 libgmp.so.10 libkrb5support.so.0 libkeyutils.so.1 libpcre2-8.so.0 libuuid.so.1 liblz4.so.1 libgcrypt.so.20 libbz2.so.1 -``` +You can find a reference to all the relevant Debian packages for the service to function [in the Dockerfile](https://github.com/grafana/grafana-image-renderer/blob/master/Dockerfile). +If you are using an operating system that is not Debian 12, you should look up what each of those packages are called on your system. ## Certificate signed by internal certificate authorities @@ -127,6 +86,9 @@ If this happens, then you have to add the certificate to the trust store. If you [root@server ~]# chown -R grafana: /usr/share/grafana/.pki/nssdb ``` +You may also have to use other tooling than `certutil`, such as `update-ca-certificates` and its accompanying paths. +This depends on the Linux distribution you use; distributions often have a wiki with this type of information. + **Windows:** ``` @@ -138,17 +100,30 @@ certutil –addstore "Root" /internal-root-ca.crt.pem ```Dockerfile FROM grafana/grafana-image-renderer:latest +# Elevate our permissions to access system resources. USER root -RUN apk add --no-cache nss-tools +RUN mkdir -p /usr/local/share/ca-certificates/ +# Convert from .pem to .crt +RUN openssl x509 -inform PEM -in rootCA.pem -out /usr/local/share/ca-certificates/rootCA.crt -USER grafana +# Regenerate the CA certificates in the container. +RUN update-ca-certificates --fresh -COPY internal-root-ca.crt.pem /etc/pki/tls/certs/internal-root-ca.crt.pem -RUN mkdir -p /home/grafana/.pki/nssdb -RUN certutil -d sql:/home/grafana/.pki/nssdb -A -n internal-root-ca -t C -i /etc/pki/tls/certs/internal-root-ca.crt.pem +# Reassume the nonroot user for the service execution. +USER nonroot + +# Some CA certificates also need to explicitly be included in the user's network security services database. +# certutil is shipped in v4.0.8 and onwards of the image. +RUN mkdir -p /home/nonroot/.pki/nssdb +RUN certutil -d sql:/home/nonroot/.pki/nssdb -A -n internal-root-ca -t C -i /usr/local/share/ca-certificates/rootCA.crt ``` +{{< admonition type="note" >}} +The container image was based on Alpine until v4.0.0. +After this point, it is based on distroless Debian. +{{< /admonition >}} + ## Custom Chrome/Chromium As a last resort, if you already have [Chrome](https://www.google.com/chrome/) or [Chromium](https://www.chromium.org/) diff --git a/docs/sources/setup-grafana/installation/helm/index.md b/docs/sources/setup-grafana/installation/helm/index.md index 9551abe3a44..a77677b4f9b 100644 --- a/docs/sources/setup-grafana/installation/helm/index.md +++ b/docs/sources/setup-grafana/installation/helm/index.md @@ -109,7 +109,6 @@ When you create a new namespace in Kubernetes, you can better organize, allocate ``` Where: - - `helm install`: Installs the chart by deploying it on the Kubernetes cluster - `my-grafana`: The logical chart name that you provided - `grafana/grafana`: The repository and package name to install @@ -147,7 +146,6 @@ This section describes the steps you must complete to access Grafana via web bro ``` This command will print out the chart notes. You will the output `NOTES` that provide the complete instructions about: - - How to decode the login password for the Grafana admin account - Access Grafana service to the web browser diff --git a/docs/sources/setup-grafana/installation/kubernetes/index.md b/docs/sources/setup-grafana/installation/kubernetes/index.md index 7a9cf7fb9a7..0085a670896 100644 --- a/docs/sources/setup-grafana/installation/kubernetes/index.md +++ b/docs/sources/setup-grafana/installation/kubernetes/index.md @@ -391,9 +391,7 @@ Instead of using the `annotate` flag, you can still use the `--record` flag. How 1. In the editor, change the container image under the `kind: Deployment` section. For example: - - From - - `yaml image: grafana/grafana-oss:10.0.1` - To @@ -573,7 +571,6 @@ This section outlines general instructions for provisioning Grafana resources wi ``` You can follow the same process to provision additional Grafana resources by supplying the following folders: - - `provisioning/dashboards` - `provisioning/datasources` - `provisioning/plugins` diff --git a/docs/sources/setup-grafana/installation/mac/index.md b/docs/sources/setup-grafana/installation/mac/index.md index f4c3f5d7342..16a064ab063 100644 --- a/docs/sources/setup-grafana/installation/mac/index.md +++ b/docs/sources/setup-grafana/installation/mac/index.md @@ -35,7 +35,6 @@ To install Grafana on macOS using Homebrew, complete the following steps: ``` The brew page downloads and untars the files into: - - `/usr/local/Cellar/grafana/[version]` (Intel Silicon) - `/opt/homebrew/Cellar/grafana/[version]` (Apple Silicon) diff --git a/docs/sources/setup-grafana/set-up-https.md b/docs/sources/setup-grafana/set-up-https.md index b9823b47f2a..00c8838be94 100644 --- a/docs/sources/setup-grafana/set-up-https.md +++ b/docs/sources/setup-grafana/set-up-https.md @@ -131,7 +131,6 @@ The instructions provided in this section are for a Debian-based Linux system. F ``` These commands: - - Uninstall `certbot` from your system if it has been installed using a package manager - Install `certbot` using `snapd` diff --git a/docs/sources/shared/datasources/datasouce-authentication.md b/docs/sources/shared/datasources/datasouce-authentication.md index 4582eb48593..cb51a39774a 100644 --- a/docs/sources/shared/datasources/datasouce-authentication.md +++ b/docs/sources/shared/datasources/datasouce-authentication.md @@ -18,7 +18,6 @@ labels: To set up authentication: 1. Select an authentication method from the drop-down list: - - **Basic authentication**: Authenticates your data source using a username and password - **Forward OAuth identity**: Forwards the OAuth access token and the OIDC ID token, if available, of the user querying to the data source - **No authentication**: No authentication is required to access the data source diff --git a/docs/sources/shared/upgrade/upgrade-common-tasks.md b/docs/sources/shared/upgrade/upgrade-common-tasks.md index e6a252d43e4..0667dca09e6 100644 --- a/docs/sources/shared/upgrade/upgrade-common-tasks.md +++ b/docs/sources/shared/upgrade/upgrade-common-tasks.md @@ -66,7 +66,6 @@ To upgrade Grafana installed using RPM or YUM complete the following steps: This enables you to upgrade Grafana without the risk of losing your configuration changes. 1. Perform one of the following steps based on your installation. - - If you [downloaded an RPM package](https://grafana.com/grafana/download) to install Grafana, then complete the steps documented in [Install Grafana on Red Hat, RHEL, or Fedora](https://grafana.com/docs/grafana//setup-grafana/installation/redhat-rhel-fedora/) or [Install Grafana on SUSE or openSUSE](https://grafana.com/docs/grafana///setup-grafana/installation/suse-opensuse/) to upgrade Grafana. - If you used the Grafana YUM repository, run the following command: diff --git a/docs/sources/shared/visualizations/cell-options.md b/docs/sources/shared/visualizations/cell-options.md new file mode 100644 index 00000000000..03c0e4a76dc --- /dev/null +++ b/docs/sources/shared/visualizations/cell-options.md @@ -0,0 +1,10 @@ +--- +title: Cell options +--- + + +| Option | Description | +| ------ | ----------- | +| Wrap text |

Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](https://grafana.com/docs/grafana//panels-visualizations/configure-overrides/), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch.

Text wrapping is in [public preview](https://grafana.com/docs/release-life-cycle/#public-preview), however, it’s available to use by default.

| +| Cell value inspect |

Enables value inspection from table cells. When the switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer which contains two tabs: **Plain text** and **Code editor**.

Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. However, you can switch back and forth between tabs.

| + diff --git a/docs/sources/tutorials/alerting-get-started-pt2/index.md b/docs/sources/tutorials/alerting-get-started-pt2/index.md index b64564ec74e..ff2b8ab2487 100644 --- a/docs/sources/tutorials/alerting-get-started-pt2/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt2/index.md @@ -25,6 +25,8 @@ This tutorial is a continuation of the [Grafana Alerting - Create and receive yo In this guide, we dig into more complex yet equally fundamental elements of Grafana Alerting: **alert instances** and **notification policies**. +{{< youtube id="nI-_MEnFBQs" >}} + After introducing each component, you will learn how to: - Configure an alert rule that returns more than one alert instance @@ -49,17 +51,14 @@ Learning about alert instances and notification policies is useful if you have m There are different ways you can follow along with this tutorial. - **Grafana Cloud** - - As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user). Continue to [Alert instances](#alert-instances). - **Interactive learning environment** - - Alternatively, you can try out this example in our interactive learning environment: [Get started with Grafana Alerting - Alert routing](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt2/). It's a fully configured environment with all the dependencies already installed. - **Grafana OSS** - - If you opt to run a Grafana stack locally, ensure you have the following applications installed: - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) @@ -250,7 +249,6 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da The above CSV data simulates a data source returning multiple time series, each leading to the creation of an alert instance for that specific time series. Note that the data returned matches the example in the [Alert instance](#alert-instances) section. 1. In the **Alert condition** section: - - Keep `Last` as the value for the reducer function (`WHEN`), and `IS ABOVE 1000` as the threshold value. This is the value above which the alert rule should trigger. 1. Click **Preview alert rule condition** to run the queries. diff --git a/docs/sources/tutorials/alerting-get-started-pt3/index.md b/docs/sources/tutorials/alerting-get-started-pt3/index.md index fa7aa1c55d7..20141eee062 100644 --- a/docs/sources/tutorials/alerting-get-started-pt3/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt3/index.md @@ -39,6 +39,8 @@ refs: This tutorial is a continuation of the [Get started with Grafana Alerting - Alert routing](http://www.grafana.com/tutorials/alerting-get-started-pt2/) tutorial. +{{< youtube id="WZ8gqKIQ5Oc" >}} + Grouping in Grafana Alerting reduces notification noise by combining related alert instances into a single, concise notification. This is useful for on-call engineers, ensuring they focus on resolving incidents instead of sorting through a flood of notifications. Grouping is configured using labels in the notification policy. These labels reference those generated by alert instances or configured by the user. @@ -68,17 +70,14 @@ In this tutorial, you will: There are different ways you can follow along with this tutorial. - **Grafana Cloud** - - As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user). Continue to [How alert rule grouping works](#how-alert-rule-grouping-works). - **Interactive learning environment** - - Alternatively, you can try out this example in our interactive learning environment: [Get started with Grafana Alerting - Grouping](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt3/). It's a fully configured environment with all the dependencies already installed. - **Grafana OSS** - - If you opt to run a Grafana stack locally, ensure you have the following applications installed: - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) @@ -227,16 +226,13 @@ Following the above example, [notification policies](ref:notification-policies) 1. Sign in to Grafana: - - **Grafana Cloud** users: Log in via Grafana Cloud. - **OSS users**: Go to [http://localhost:3000](http://localhost:3000). 1. Navigate to **Notification Policies**: - - Go to **Alerts & IRM > Alerting > Notification Policies**. 1. Add a child policy: - - In the Default policy, click **+ New child policy**. - **Label**: `region` - **Operator**: `=` @@ -245,31 +241,26 @@ Following the above example, [notification policies](ref:notification-policies) This label matches alert rules where the region label is us-west. 1. Choose a **Contact point**: - - Select **Webhook**. If you don’t have any contact points, add a [Contact point](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/#add-a-contact-point). 1. Enable Continue matching: - - Turn on **Continue matching subsequent sibling nodes** so the evaluation continues even after one or more labels (i.e. region label) match. 1. Override grouping settings: - - Toggle **Override grouping**. - **Group by**: Add `region` as label. Remove any existing labels. **Group by** consolidates alerts that share the same grouping label into a single notification. For example, all alerts with `region=us-west` will be combined into one notification, making it easier to manage and reducing alert fatigue. 1. Set custom timing: - - Toggle **Override general timings**. - **Group interval**: `2m`. This ensures follow-up notifications for the same alert group will be sent at intervals of 2 minutes. While the default is 5 minutes, we chose 2 minutes here to provide faster feedback for demonstration purposes. **Timing options** control how often notifications are sent and can help balance timely alerting with minimizing noise. 1. Save and repeat: - - Repeat the steps above for `region = us-east` but without overriding grouping and timing options. Use a different webhook endpoint as the contact point. {{< figure src="/media/docs/alerting/notificaiton-policies-region.png" max-width="750px" alt="Two nested notification policies to route and group alert notifications" >}} @@ -290,7 +281,6 @@ Following the above example, [notification policies](ref:notification-policies) 1. Visit [http://localhost:3000](http://localhost:3000), where Grafana should be running 1. Navigate to **Alerts & IRM > Alerting > Notification policies**. 1. In the Default policy, click **+ New child policy**. - - In the Default policy, click **+ New child policy**. - **Label**: `region` - **Operator**: `=` @@ -299,31 +289,26 @@ Following the above example, [notification policies](ref:notification-policies) This label matches alert rules where the region label is us-west 1. Choose a **Contact point**: - - Select **Webhook**. If you don’t have any contact points, add a Contact point. 1. Enable Continue matching: - - Turn on **Continue matching subsequent sibling nodes** so the evaluation continues even after one or more labels (i.e. region label) match. 1. Override grouping settings: - - Toggle **Override grouping**. - **Group by**: `region`. **Group by** consolidates alerts that share the same grouping label into a single notification. For example, all alerts with `region=us-west` will be combined into one notification, making it easier to manage and reducing alert fatigue. 1. Set custom timing: - - Toggle **Override general timings**. - **Group interval**: `2m`. This ensures follow-up notifications for the same alert group will be sent at intervals of 2 minutes. While the default is 5 minutes, we chose 2 minutes here to provide faster feedback for demonstration purposes. **Timing options** control how often notifications are sent and can help balance timely alerting with minimizing noise. 1. Save and repeat: - - Repeat for `region = us-east` with a different webhook or a different contact point. **Note**: Label matchers are combined using the `AND` logical operator. This means that all matchers must be satisfied for a rule to be linked to a policy. If you attempt to use the same label key (e.g., region) with different values (e.g., us-west and us-east), the condition will not match, because it is logically impossible for a single key to have multiple values simultaneously. @@ -355,7 +340,6 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da 1. From the drop-down menu, select **TestData** data source. 1. From **Scenario** select **CSV Content**. 1. Copy in the following CSV data: - - Select **TestData** as the data source. - Set **Scenario** to **CSV Content**. - Use the following CSV data: @@ -375,7 +359,6 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da The returned data simulates a data source returning multiple time series, each leading to the creation of an alert instance for that specific time series. 1. In the **Alert condition** section: - - Keep `Last` as the value for the reducer function (`WHEN`), and `IS ABOVE 75` as the threshold value. This is the value above which the alert rule should trigger. 1. Click **Preview alert rule condition** to run the queries. diff --git a/docs/sources/tutorials/alerting-get-started-pt4/index.md b/docs/sources/tutorials/alerting-get-started-pt4/index.md index ce34258da51..5fe4d1afbff 100644 --- a/docs/sources/tutorials/alerting-get-started-pt4/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt4/index.md @@ -100,17 +100,14 @@ There are different ways you can follow along with this tutorial. > Note: Some of the templating features in Grafana Alerting discussed in this tutorial are currently available in Grafana Cloud but have not yet been released to the Open Source (OSS) version. - **Grafana Cloud** - - As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user). Continue to [how templating works](#how-templating-works). - **Interactive learning environment** - - Alternatively, you can try out this example in our interactive learning environment: [Get started with Grafana Alerting - Templating](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt4/). It's a fully configured environment with all the dependencies already installed. - **Grafana OSS** - - If you opt to run a Grafana stack locally, ensure you have the following applications installed: - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) @@ -215,7 +212,6 @@ Now that we've introduced how templating works, let’s move on to the next step ### Create an alert rule 1. Sign in to Grafana: - - **Grafana Cloud** users: Log in via Grafana Cloud. - **OSS users**: Go to [http://localhost:3000](http://localhost:3000). @@ -224,7 +220,6 @@ Now that we've introduced how templating works, let’s move on to the next step - Click **+ New alert rule**. - Enter an **alert rule name**. Name it `High CPU usage` 1. **Define query an alert condition** section: - - Select TestData data source from the drop-down menu. [TestData](https://grafana.com/docs/grafana/latest/datasources/testdata/) is included in the demo environment. If you’re working in Grafana Cloud or your own local Grafana instance, you can add the data source through the Connections menu. @@ -243,7 +238,6 @@ Now that we've introduced how templating works, let’s move on to the next step This dataset simulates a data source returning multiple time series, with each time series generating a separate alert instance. 1. **Alert condition** section: - - Keep Last as the value for the reducer function (`WHEN`), and `IS ABOVE 75` as the threshold value, representing CPU usage above 75% .This is the value above which the alert rule should trigger. - Click **Preview alert rule condition** to run the queries. @@ -252,7 +246,6 @@ Now that we've introduced how templating works, let’s move on to the next step {{< figure src="/media/docs/alerting/part-4-firing-instances-preview.png" max-width="1200px" caption="Preview of a query returning alert instances" >}} 1. Add folders and labels section: - - In **Folder**, click **+ New folder** and enter a name. For example: `System metrics` . This folder contains our alert rules. Note: while it's possible to template labels here, in this tutorial, we focus on templating the summary and annotations fields instead. @@ -265,13 +258,11 @@ Now that we've introduced how templating works, let’s move on to the next step 1. **Configure notifications** section: Select who should receive a notification when an alert rule fires. - - Select a **Contact point**. If you don’t have any contact points, click _View or create contact points_. 1. **Configure notification message** section: In this step, you’ll configure the **summary** and **description** annotations to make your alert notifications informative and easy to understand. These annotations use templates to dynamically include key information about the alert. - - **Summary** annotation: Enter the following code as the value for the annotation.: ```go @@ -412,7 +403,6 @@ In this tutorial, we learned how to use templating in Grafana Alerting to create To deepen your understanding of Grafana’s templating, explore the following resources: - **Overview of the functions and operators used in templates**: - - [Notification template language](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/language/) - [Alert rule template language](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/language/) diff --git a/docs/sources/tutorials/alerting-get-started-pt5/index.md b/docs/sources/tutorials/alerting-get-started-pt5/index.md index 91a1c1e4141..fe89a2a3b18 100644 --- a/docs/sources/tutorials/alerting-get-started-pt5/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt5/index.md @@ -37,7 +37,6 @@ In this tutorial you will learn how to: ## Before you begin - **Interactive learning environment** - - Alternatively, you can [try out this example in our interactive learning environment](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt5/). It’s a fully configured environment with all the dependencies already installed. - **Grafana OSS** @@ -174,14 +173,12 @@ Notification policies route alert instances to contact points via label matchers Although our application doesn't explicitly include an `environment` label, we can rely on other labels like `instance` or `deployment`, which may contain keywords (like prod or staging) that indicate the environment. 1. Sign in to Grafana: - - **Grafana Cloud** users: Log in via Grafana Cloud. - **OSS users**: Go to [http://localhost:3000](http://localhost:3000). 1. Navigate to **Alerts & IRM > Alerting > Notification Policies**. 1. Add a child policy: - - In the **Default policy**, click **+ New child policy**. - **Label**: `environment`. - **Operator**: `=`. @@ -189,17 +186,14 @@ Although our application doesn't explicitly include an `environment` label, we c - This label matches alert rules where the environment label is `prod`. 1. Choose a **contact point**: - - If you don’t have any contact points, add a [Contact point](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/#add-a-contact-point). For a quick test, you can use a public webhook from [webhook.site](https://webhook.site/) to capture and inspect alert notifications. If you choose this method, select **Webhook** from the drop-down menu in contact points. 1. Enable continue matching: - - Turn on **Continue matching subsequent sibling nodes** so the evaluation continues even after one or more labels (i.e., _environment_ labels) match. 1. Save and repeat - - Create another child policy by following the same steps. - Use `environment = staging` as the label/value pair. - Feel free to use a different contact point. @@ -234,7 +228,6 @@ Make it short and descriptive, as this will appear in your alert notification. F ``` 1. **Alert condition** section: - - Enter `75` as the value for **WHEN QUERY IS ABOVE** to set the threshold for the alert. - Click **Preview alert rule condition** to run the queries. diff --git a/docs/sources/tutorials/alerting-get-started-pt6/index.md b/docs/sources/tutorials/alerting-get-started-pt6/index.md index 8f4b3eeff83..470330237e2 100644 --- a/docs/sources/tutorials/alerting-get-started-pt6/index.md +++ b/docs/sources/tutorials/alerting-get-started-pt6/index.md @@ -39,7 +39,6 @@ In this tutorial you will learn how to: ## Before you begin - **Interactive learning environment** - - Alternatively, you can [try out this example in our interactive learning environment](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt6/). It’s a fully configured environment with all the dependencies already installed. - **Grafana OSS** @@ -150,12 +149,10 @@ To keep track of these metrics you can set up a visualization for CPU usage and The time-series visualization supports alert rules to provide more context in the form of annotations and alert rule state. Follow these steps to create a visualization to monitor the application’s metrics. 1. Log in to Grafana: - - Navigate to [http://localhost:3000](http://localhost:3000), where Grafana should be running. - Username and password: `admin` 1. Create a time series panel: - - Navigate to **Dashboards**. - Click **+ Create dashboard**. - Click **+ Add visualization**. @@ -163,7 +160,6 @@ The time-series visualization supports alert rules to provide more context in th - Enter a title for your panel, e.g., **CPU and Memory Usage**. 1. Add queries for metrics: - - In the query area, copy and paste the following PromQL query: ** switch to **Code** mode if not already selected ** @@ -177,7 +173,6 @@ The time-series visualization supports alert rules to provide more context in th This query should display the simulated CPU usage data for the **prod** environment. 1. Add memory usage query: - - Click **+ Add query**. - In the query area, paste the following PromQL query: @@ -219,7 +214,6 @@ Make it short and descriptive, as this will appear in your alert notification. F ``` 1. **Alert condition** - - Enter 75 as the value for **WHEN QUERY IS ABOVE** to set the threshold for the alert. - Click **Preview alert rule condition** to run the queries. diff --git a/docs/sources/tutorials/alerting-get-started/index.md b/docs/sources/tutorials/alerting-get-started/index.md index 542190188f1..69107963b35 100644 --- a/docs/sources/tutorials/alerting-get-started/index.md +++ b/docs/sources/tutorials/alerting-get-started/index.md @@ -57,17 +57,14 @@ After you have completed Part 1, don’t forget to explore the advanced but esse There are different ways you can follow along with this tutorial. - **Grafana Cloud** - - As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user). Continue to [Create a contact point](#create-a-contact-point). - **Interactive learning environment** - - Alternatively, you can [try out this example in our interactive learning environment](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started/). It's a fully configured environment with all the dependencies already installed. - **Grafana OSS** - - If you opt to run a Grafana stack locally, ensure you have the following applications installed: - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) @@ -194,7 +191,6 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da 1. Select the **TestData** data source from the drop-down menu. 1. In the **Alert condition** section: - - Keep **Random Walk** as the _Scenario_. - Keep `Last` as the value for the reducer function (`WHEN`), and `IS ABOVE 0` as the threshold value. This is the value above which the alert rule should trigger. diff --git a/docs/sources/tutorials/create-alerts-with-logs/index.md b/docs/sources/tutorials/create-alerts-with-logs/index.md index 873a2e7094f..aa5ee2eaade 100644 --- a/docs/sources/tutorials/create-alerts-with-logs/index.md +++ b/docs/sources/tutorials/create-alerts-with-logs/index.md @@ -65,12 +65,10 @@ There are different ways you can follow along with this tutorial. - **Grafana OSS** To run a Grafana stack locally, ensure you have the following applications installed: - - [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows) - [Git](https://git-scm.com/) - **Interactive learning environment** - - Alternatively, you can [try out this example in our interactive learning environment](https://killercoda.com/grafana-labs/course/grafana/alerting-loki-logs). It's a fully configured environment with all the dependencies already installed. ## Set up the Grafana stack @@ -227,7 +225,6 @@ In this section, we use the default options for Grafana-managed alert rule creat {{< /docs/ignore >}} 1. In the **Alert condition** section: - - Keep `Last` as the value for the reducer function (`WHEN`), and `0` as the threshold value. This is the value above which the alert rule should trigger. 1. Click **Preview alert rule condition** to run the query. diff --git a/docs/sources/tutorials/grafana-fundamentals/index.md b/docs/sources/tutorials/grafana-fundamentals/index.md index 20f9616fea8..0b8ef024707 100644 --- a/docs/sources/tutorials/grafana-fundamentals/index.md +++ b/docs/sources/tutorials/grafana-fundamentals/index.md @@ -375,7 +375,6 @@ The most basic alert rule consists of two parts: > {{< /docs/ignore >}} Some popular channels include: - - [Email](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/configure-email/) - [Webhooks](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/) - [Telegram](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram/) diff --git a/docs/sources/upgrade-guide/upgrade-v12.1/index.md b/docs/sources/upgrade-guide/upgrade-v12.1/index.md new file mode 100644 index 00000000000..74095298c90 --- /dev/null +++ b/docs/sources/upgrade-guide/upgrade-v12.1/index.md @@ -0,0 +1,22 @@ +--- +description: Guide for upgrading to Grafana v12.1 +keywords: + - grafana + - configuration + - documentation + - upgrade + - '12.1' +title: Upgrade to Grafana v12.1 +menuTitle: Upgrade to v12.1 +weight: 499 +--- + +# Upgrade to Grafana v12.1 + +{{< docs/shared lookup="upgrade/intro_2.md" source="grafana" version="" >}} + +{{< docs/shared lookup="back-up/back-up-grafana.md" source="grafana" version="" leveloffset="+1" >}} + +{{< docs/shared lookup="upgrade/upgrade-common-tasks.md" source="grafana" version="" >}} + +## Technical notes diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md index bd0ac788b43..6467fb25ded 100644 --- a/docs/sources/upgrade-guide/when-to-upgrade/index.md +++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md @@ -46,7 +46,7 @@ We provide release documentation in multiple places to address different needs: ## When to expect releases -Currently, Grafana is on a monthly release cycle. Here’s a look at scheduled releases for 2025: +Grafana currently follows a monthly release schedule. Below are the planned releases for 2025, though these dates may be subject to change: | **Release date** | **Grafana versions** | **Release type** | | ---------------- | ------------------------- | ---------------- | @@ -110,28 +110,26 @@ Here is an overview of version support through 2026: | 11.0.x | May 14, 2024 | February 14, 2025 | Not Supported | | 11.1.x | June 25, 2024 | April 23, 2025 | Not Supported | | 11.2.x | August 27, 2024 | May 27, 2025 | Not Supported | -| 11.3.x | October 22, 2024 | July 22, 2025 | Security & Critical Bugs Only | +| 11.3.x | October 22, 2024 | July 22, 2025 | Not Supported | | 11.4.x | December 5, 2024 | September 5, 2025 | Security & Critical Bugs Only | | 11.5.x | January 28, 2025 | October 28, 2025 | Security & Critical Bugs Only | -| 11.6.x (Last minor of 11) | March 25, 2025 | May 25, 2026 | Security & Critical Bugs Only | -| 12.0.x | May 5, 2025 | February 5, 2026 | Full Support until next minor | +| 11.6.x (Last minor of 11) | March 25, 2025 | June 25, 2026 | Security & Critical Bugs Only | +| 12.0.x | May 5, 2025 | February 5, 2026 | Security & Critical Bugs Only | | 12.1.x | July 22, 2025 | April 22, 2026 | Full Support until next minor | -| 12.2.x | September 23, 2025 | June 23, 2026 | Full Support until next minor | -| 12.3.x | November 18, 2025 | August 18, 2026 | Full Support until next minor | +| 12.2.x | September 23, 2025 | June 23, 2026 | Yet to be released | +| 12.3.x | November 18, 2025 | August 18, 2026 | Yet to be released | ## How are these versions supported? The level of support changes as new versions of Grafana are released. Here are the key details: - **Full Support**: - - All new features - All bug fixes - Security patches - Regular updates - **Security & Critical Bugs Only**: - - Security vulnerability patches - Critical bug fixes that cause feature degradation - No new features diff --git a/docs/sources/whatsnew/_index.md b/docs/sources/whatsnew/_index.md index 6c0a1386693..bcc9f3b4b4d 100644 --- a/docs/sources/whatsnew/_index.md +++ b/docs/sources/whatsnew/_index.md @@ -76,6 +76,7 @@ For a complete list of every change, with links to pull requests and related iss ## Grafana 12 +- [What's new in 12.1](https://grafana.com/docs/grafana//whatsnew/whats-new-in-v12-1) - [What's new in 12.0](https://grafana.com/docs/grafana//whatsnew/whats-new-in-v12-0) ## Grafana 11 diff --git a/docs/sources/whatsnew/whats-new-in-v11-3.md b/docs/sources/whatsnew/whats-new-in-v11-3.md index aafe83f2695..5eb55341a04 100644 --- a/docs/sources/whatsnew/whats-new-in-v11-3.md +++ b/docs/sources/whatsnew/whats-new-in-v11-3.md @@ -62,7 +62,6 @@ For configuring controls outside of playlist playback, you can use the following - The [variable usage check](https://grafana.com/docs/grafana//dashboards/variables/inspect-variable/) is not yet available. - Editing a panel: - - The **Library panels** tab is not available anymore. You can replace a library panel from the panel menu. - The **Overrides** tab is not in panel options (coming in Grafana v11.3.0). Overrides are shown at the bottom of the option list. - The drop-down menu to collapse the visualization picker is missing (coming in Grafana v11.3.0). diff --git a/docs/sources/whatsnew/whats-new-in-v12-1.md b/docs/sources/whatsnew/whats-new-in-v12-1.md new file mode 100644 index 00000000000..27e4f848682 --- /dev/null +++ b/docs/sources/whatsnew/whats-new-in-v12-1.md @@ -0,0 +1,53 @@ +--- +description: Feature and improvement highlights for Grafana v12.1 +keywords: + - grafana + - new + - documentation + - '12.1' + - release notes +labels: + products: + - cloud + - enterprise + - oss +title: What's new in Grafana v12.1 +posts: + - title: Data sources + items: + - whats-new/2025-06-11-logicmonitor-enterprise-data-source-for-grafana.md + - whats-new/2025-06-20-support-for-service-account-impersonation-in-bigquery.md + - whats-new/2025-07-08-keep-instances-running-smoothly-with-grafana-advisor.md + - title: Alerting + items: + - whats-new/2025-07-11-alert-rule-list-page-updates.md + - whats-new/2025-07-09-active-time-intervals-in-grafana-alerting.md + - whats-new/2025-07-14-add-ability-to-import-rules-to-gma-from-prometheus-yaml.md + - title: Dashboards and visualizations + items: + - whats-new/2025-06-30-transformations-regression-analysis.md + - whats-new/2025-07-14-custom-variable-support-in-visualization-actions.md + - whats-new/2025-07-10-server-configurable-quick-time-ranges-for-dashboards.md + - whats-new/2025-07-14-enhanced-custom-currency-format-display-exact-financial-values.md + - title: Authentication and authorization + items: + - whats-new/2025-07-14-entra-id-workload-identity-support.md +whats_new_grafana_version: 12.1 +weight: -50 +--- + +# What’s new in Grafana v12.1 + +It’s 12.1 time! We’re really excited about GA for a new alert rule page that makes it easier to find what you need quickly, as well as the regression analysis transformation, helping you predict future data values or estimate missing data points that might not be exactly represented in the original dataset. Also, visualization actions now support custom variables. When triggered, actions prompt you to input whatever custom variable was defined, allowing you to tailor requests in real time without modifying dashboard configuration. This is super helpful for when you’re triggering alerts, filtering API calls, or sending user-defined parameters to external systems. And you can try out Grafana Advisor, which automatically detects plugin, data source, and SSO issues, keeping your Grafana instance healthy and secure. + +Speaking of security, Grafana now supports Entra Workload Identity, simplifying OAuth and increasing security for instances using Microsoft Azure. Thanks to community contributor [mehighlow](https://github.com/mehighlow) for this feature! + +We have one more community contributor to thank for this release. [Chris Hodges](https://github.com/chodges15) delivered server-configurable quick time ranges for dashboards. Now you can define custom time range presets for the time picker on dashboards, perfect for teams that routinely analyze specific, context-driven time windows. + +Keep reading to learn about what else we have in store for 12.1. + + + +For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). For the specific steps we recommend when you upgrade to v12.1, check out our [Upgrade Guide](https://grafana.com/docs/grafana//upgrade-guide/upgrade-v12.1/). + +{{< docs/whats-new >}} diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-edit-panel-title-description.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-edit-panel-title-description.spec.ts index 30373362528..425f01db823 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboards-edit-panel-title-description.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboards-edit-panel-title-description.spec.ts @@ -51,8 +51,10 @@ test.describe( // Get the tooltip ID from the aria-describedby attribute const tooltipId = await descriptionIcon.getAttribute('aria-describedby'); - const tooltip = page.locator(`[id="${tooltipId}"]`); - await expect(tooltip).toHaveText(`${newDescription}\n`); + await expect(async () => { + const tooltip = page.locator(`[id="${tooltipId}"]`); + await expect(tooltip).toHaveText(`${newDescription}\n`); + }).toPass(); }); } ); diff --git a/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts b/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts index 7f88e462deb..7dfbf455176 100644 --- a/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @@ -7,6 +7,12 @@ const NUM_ROOT_DASHBOARDS = 60; const NUM_NESTED_FOLDERS = 60; const NUM_NESTED_DASHBOARDS = 60; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + // TODO change this test so it doesn't conflict with the existing dashboard browse test // probably needs a separate user test.describe.skip( @@ -101,7 +107,7 @@ test.describe.skip( await expect(page.getByText('Nested folder 00')).toBeVisible(); // Get the table body container for scrolling - const tableBody = page.getByTestId(selectors.pages.BrowseDashboards.table.body).locator('> div'); + const tableBody = page.getByRole('grid'); // Scroll the page and check visibility of next set of items await tableBody.evaluate((el) => el.scrollTo(0, 2100)); diff --git a/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts b/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts index 79ae94c5e96..dcebf98884d 100644 --- a/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts @@ -2,6 +2,12 @@ import { test, expect } from '@grafana/plugin-e2e'; import testDashboard from '../dashboards/DashboardLiveTest.json'; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + test.describe( 'Dashboard Live streaming support', { @@ -31,18 +37,10 @@ test.describe( } }); - test('Should receive streaming data', async ({ gotoDashboardPage, selectors }) => { + test('Should receive streaming data', async ({ gotoDashboardPage, selectors, page }) => { const dashboardPage = await gotoDashboardPage({ uid: dashboardUID }); await expect(dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Live'))).toBeVisible(); - await expect - .poll( - async () => - await dashboardPage - .getByGrafanaSelector(selectors.components.Panels.Visualization.Table.body) - .getByRole('row') - .count() - ) - .toBeGreaterThan(5); + await expect.poll(async () => await page.getByRole('grid').getByRole('row').count()).toBeGreaterThan(5); }); } ); diff --git a/e2e-playwright/dashboards-suite/dashboard-timepicker.spec.ts b/e2e-playwright/dashboards-suite/dashboard-timepicker.spec.ts index ac505aa9cd4..e57c56b5afe 100644 --- a/e2e-playwright/dashboards-suite/dashboard-timepicker.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-timepicker.spec.ts @@ -42,6 +42,9 @@ test.describe( await page.getByTestId(selectors.components.TimeZonePicker.containerV2).click(); await page.getByRole('option', { name: 'Asia/Tokyo' }).click(); await page.getByTestId(selectors.components.UserProfile.preferencesSaveButton).click(); + // wait for the page to reload before trying to navigate, otherwise this can cause flakes + // see e.g. https://github.com/microsoft/playwright/issues/21451#issuecomment-1502251404 + await page.waitForURL('/profile'); // Open dashboard with time range from 8th to end of 10th. // Will be Tokyo time because of above preference diff --git a/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts b/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts index 10df0887a0f..dc625ea4adc 100644 --- a/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts +++ b/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts @@ -2,13 +2,19 @@ import { test, expect } from '@grafana/plugin-e2e'; const DASHBOARD_ID = 'P2jR04WVk'; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + test.describe( 'Panels test: Geomap spatial operations', { tag: ['@panels'], }, () => { - test('Tests location auto option', async ({ gotoDashboardPage, selectors }) => { + test('Tests location auto option', async ({ gotoDashboardPage, selectors, page }) => { const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_ID, queryParams: new URLSearchParams({ editPanel: '1' }), @@ -40,12 +46,12 @@ test.describe( await locationInput.press('Enter'); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleTableView).click({ force: true }); - const tableHeader = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header); + const tableHeader = page.getByRole('grid').getByRole('row').first(); await expect(tableHeader).toBeVisible(); await expect(tableHeader.getByText('Point')).toBeVisible(); }); - test('Tests location coords option', async ({ gotoDashboardPage, dashboardPage, selectors }) => { + test('Tests location coords option', async ({ gotoDashboardPage, dashboardPage, selectors, page }) => { await gotoDashboardPage({ uid: DASHBOARD_ID, queryParams: new URLSearchParams({ editPanel: '1' }) }); await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Transformations')).click(); @@ -90,7 +96,7 @@ test.describe( await longitudeInput.press('Enter'); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleTableView).click({ force: true }); - const tableHeader = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header); + const tableHeader = page.getByRole('grid').getByRole('row').first(); await expect(tableHeader).toBeVisible(); await expect(tableHeader.getByText('Point')).toBeVisible(); }); @@ -99,6 +105,7 @@ test.describe( gotoDashboardPage, dashboardPage, selectors, + page, }) => { await gotoDashboardPage({ uid: DASHBOARD_ID, queryParams: new URLSearchParams({ editPanel: '1' }) }); @@ -136,12 +143,12 @@ test.describe( await geohashFieldInput.press('Enter'); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleTableView).click({ force: true }); - const tableHeader = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header); + const tableHeader = page.getByRole('grid').getByRole('row').first(); await expect(tableHeader).toBeVisible(); await expect(tableHeader.getByText('State 1')).toBeVisible(); }); - test('Tests location lookup option', async ({ gotoDashboardPage, dashboardPage, selectors }) => { + test('Tests location lookup option', async ({ gotoDashboardPage, dashboardPage, selectors, page }) => { await gotoDashboardPage({ uid: DASHBOARD_ID, queryParams: new URLSearchParams({ editPanel: '1' }) }); await dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('Transformations')).click(); @@ -186,7 +193,7 @@ test.describe( await gazetteerFieldInput.press('Enter'); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleTableView).click({ force: true }); - const tableHeader = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header); + const tableHeader = page.getByRole('grid').getByRole('row').first(); await expect(tableHeader).toBeVisible(); await expect(tableHeader.getByText('Geometry')).toBeVisible(); }); diff --git a/e2e-playwright/panels-suite/panelEdit_base.spec.ts b/e2e-playwright/panels-suite/panelEdit_base.spec.ts index 66584d0387b..da98e8089f5 100644 --- a/e2e-playwright/panels-suite/panelEdit_base.spec.ts +++ b/e2e-playwright/panels-suite/panelEdit_base.spec.ts @@ -2,6 +2,12 @@ import { test, expect } from '@grafana/plugin-e2e'; const PANEL_UNDER_TEST = 'Lines 500 data points'; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + test.describe( 'Panels test: Panel edit base', { @@ -65,7 +71,7 @@ test.describe( // Check that table view works await expect(dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.loadingBar(''))).toHaveCount(0); await dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleTableView).click({ force: true }); - const tableHeader = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header); + const tableHeader = page.getByRole('grid').getByRole('row').first(); await expect(tableHeader).toBeVisible(); await expect(tableHeader.getByText('A-series')).toBeVisible(); diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts new file mode 100644 index 00000000000..7052ccfc510 --- /dev/null +++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts @@ -0,0 +1,381 @@ +import { Page, Locator } from '@playwright/test'; + +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = 'dcb9f5e9-8066-4397-889e-864b99555dbb'; + +test.use({ + viewport: { width: 2000, height: 1080 }, + featureToggles: { + tableNextGen: true, + }, +}); + +// helper utils +const waitForTableLoad = async (loc: Page | Locator) => { + await expect(loc.locator('.rdg')).toBeVisible(); +}; + +const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) => + loc + .getByRole('row') + .nth(rowIdx) + .getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell') + .nth(colIdx); + +const getCellHeight = async (loc: Page | Locator, rowIdx: number, colIdx: number) => { + const cell = await getCell(loc, rowIdx, colIdx); + return (await cell.boundingBox())?.height ?? 0; +}; + +const getColumnIdx = async (loc: Page | Locator, columnName: string) => { + // find the index of the column "Long text." The kitchen sink table will change over time, but + // we can just find the column programatically and use it throughout the test. + let result = -1; + const colCount = await loc.getByRole('columnheader').count(); + for (let colIdx = 0; colIdx < colCount; colIdx++) { + const cell = await getCell(loc, 0, colIdx); + if ((await cell.textContent()) === columnName) { + result = colIdx; + break; + } + } + if (result === -1) { + throw new Error(`Could not find the "${columnName}" column in the table`); + } + return result; +}; + +test.describe( + 'Panels test: Table - Kitchen Sink', + { + tag: ['@panels'], + }, + () => { + test('Tests word wrap, hover overflow, and cell inspect', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + // to avoid a race condition when counting up , wait for react-data-grid to finish rendering. + await waitForTableLoad(page); + + const longTextColIdx = await getColumnIdx(page, 'Long Text'); + + // text wrapping is enabled by default on this panel. + await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100); + + // toggle the lorem ipsum column's wrap text toggle and confirm that the height shrinks. + await dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')) + .last() + .click(); + await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100); + + // test that hover overflow works. + const loremIpsumCell = await getCell(page, 1, longTextColIdx); + await loremIpsumCell.scrollIntoViewIfNeeded(); + await loremIpsumCell.hover(); + await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100); + await (await getCell(page, 1, longTextColIdx + 1)).hover(); + await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100); + + // enable cell inspect, confirm that hover no longer triggers. + await dashboardPage + .getByGrafanaSelector( + selectors.components.PanelEditor.OptionsPane.fieldLabel('Cell options Cell value inspect') + ) + .first() + .locator('label[for="custom.inspect"]') + .click(); + await loremIpsumCell.hover(); + await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100); + + // click cell inspect, check that cell inspection pops open in the side as we'd expect. + await loremIpsumCell.getByLabel('Inspect value').click(); + const loremIpsumText = await loremIpsumCell.textContent(); + expect(loremIpsumText).toBeDefined(); + await expect(page.getByRole('dialog').getByText(loremIpsumText!)).toBeVisible(); + }); + + test('Tests visibility and display name via overrides', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + // confirm that "State" column is hidden by default. + expect(page.getByRole('row').nth(0)).not.toContainText('State'); + + // toggle the "State" column visibility and test that it appears before re-hiding it. + // FIXME this selector is utterly godawful, but there's no way to give testIds or aria-labels or anything to + // the panel editor builder. we should fix that to make e2e's easier to write for our team. + const hideStateColumnSwitch = page.locator('[id="Override 12"]').locator('label').last(); + await hideStateColumnSwitch.click(); + expect(page.getByRole('row').nth(0)).toContainText('State'); + + // now change the display name of the "State" column. + // FIXME it would be good to have a better selector here too. + const displayNameInput = page.locator('[id="Override 12"]').locator('input[value="State"]').last(); + await displayNameInput.fill('State (renamed)'); + await displayNameInput.press('Enter'); + expect(page.getByRole('row').nth(0)).toContainText('State (renamed)'); + }); + + // we test niche cases for sorting, filtering, pagination, etc. in a unit tests already. + // we mainly want to test the happiest paths for these in e2es as well to check for integration + // issues, but the unit tests can confirm that the internal logic works as expected much more quickly and thoroughly. + // hashtag testing pyramid. + test('Tests sorting by column', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + // click the "State" column header to sort it. + const stateColumnHeader = await getCell(page, 0, 1); + + await stateColumnHeader.getByText('Info').click(); + await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending'); + expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast + + await stateColumnHeader.getByText('Info').click(); + await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending'); + expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast + + await stateColumnHeader.getByText('Info').click(); + await expect(stateColumnHeader).not.toHaveAttribute('aria-sort'); + }); + + test('Tests filtering within a column', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + await waitForTableLoad(page); + + const infoColumnIdx = await getColumnIdx(page, 'Info'); + + const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx); + + // get the first value in the "State" column, filter it out, then check that it went away. + const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!; + await stateColumnHeader + .getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton) + .click(); + const filterContainer = dashboardPage.getByGrafanaSelector( + selectors.components.Panels.Visualization.TableNG.Filters.Container + ); + + await expect(filterContainer).toBeVisible(); + + // select all, then click the first value to unselect it, filtering it out. + await filterContainer.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.SelectAll).click(); + await filterContainer.getByTitle(firstStateValue, { exact: true }).locator('label').click(); + await filterContainer.getByRole('button', { name: 'Ok' }).click(); + + // make sure the filter container closed when we clicked "Ok". + await expect(filterContainer).not.toBeVisible(); + + // did it actually filter out our value? + await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue); + }); + + test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => { + const rowRe = /([\d]+) - ([\d]+) of ([\d]+) rows/; + const getRowStatus = async (page: Page | Locator) => { + const text = (await page.getByText(rowRe).textContent()) ?? ''; + const match = text.match(rowRe); + return { + start: parseInt(match?.[1] ?? '0', 10), + end: parseInt(match?.[2] ?? '0', 10), + total: parseInt(match?.[3] ?? '0', 10), + }; + }; + + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + await page + .getByLabel(selectors.components.PanelEditor.OptionsPane.fieldLabel(`Enable pagination`), { exact: true }) + .click(); + + // because of text wrapping, we're guaranteed to only be showing a single row when we enable pagination. + await expect(page.getByText(/([\d]+) - ([\d]+) of ([\d]+) rows/)).toBeVisible(); + + // disable text wrap and see the number of rows. + await dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')) + .last() + .click(); + + // any number of rows that is not "1" is allowed here, we don't want to police the exact number of rows that + // are rendered since there are tons of factors which could effect this. we do want to grab this number for comparison + // in a second, though. + const smallRowStatus = await getRowStatus(page); + expect(smallRowStatus.end).toBeGreaterThan(1); + expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 1); + + // change cell height to Large + await dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Table Cell height')) + .locator('input') + .last() + .click(); + const largeRowStatus = await getRowStatus(page); + expect(largeRowStatus.end).toBeLessThan(smallRowStatus.end); + expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 1); + + // click a page over with the directional nav + await page.getByLabel('next page').click(); + const nextPageStatus = await getRowStatus(page); + expect(nextPageStatus.start).toBe(largeRowStatus.end + 1); + expect(nextPageStatus.end).toBe(largeRowStatus.end * 2); + expect(nextPageStatus.total).toBe(largeRowStatus.total); + + // click a page number + await page + .getByTestId('data-testid panel content') + .getByRole('navigation') + .getByText('4', { exact: true }) + .click(); + const fourthPageStatus = await getRowStatus(page); + expect(fourthPageStatus.start).toBe(largeRowStatus.end * 3 + 1); + expect(fourthPageStatus.end).toBe(largeRowStatus.end * 4); + expect(fourthPageStatus.total).toBe(largeRowStatus.total); + }); + + test('Tests DataLinks (single and multi) and actions', async ({ gotoDashboardPage, selectors, page }) => { + const addDataLink = async (title: string, url: string) => { + await dashboardPage + .getByGrafanaSelector( + selectors.components.PanelEditor.OptionsPane.fieldLabel('Data links and actions Data links') + ) + .locator('button') + .filter({ hasText: 'Add link' }) + .click(); + + // DataLinks dialog has popped open - fill it in and add a global datalink. + await expect(page.getByRole('dialog')).toBeVisible(); + await page.getByRole('dialog').locator('#link-title').fill(title); + await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').focus(); + await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').fill(url); + await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').blur(); + await page.getByRole('dialog').locator('button[aria-disabled="false"]').filter({ hasText: 'Save' }).click(); + await expect(page.getByRole('dialog')).not.toBeVisible(); + }; + + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '1' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).toBeVisible(); + + // disable text wrapping for this test to make it easier to click the links, the long lorem ipsum + // can push the links off the screen. + await dashboardPage + .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')) + .last() + .click(); + + const infoColumnIdx = await getColumnIdx(page, 'Info'); + const pillColIdx = await getColumnIdx(page, 'Pills'); + const dataLinkColIdx = await getColumnIdx(page, 'Data Link'); + + // Info column has a single DataLink by default. + const infoCell = await getCell(page, 1, infoColumnIdx); + await expect(infoCell.locator('a')).toBeVisible(); + expect(infoCell.locator('a')).toHaveAttribute('href'); + expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup'); + + // now, add a DataLink to the whole table + await addDataLink('Test link', 'https://grafana.com'); + + // add a DataLink to the whole table, all cells will now have a single link. + const colCount = await page.getByRole('row').nth(1).getByRole('gridcell').count(); + for (let colIdx = 0; colIdx < colCount; colIdx++) { + // - pills column currently does not support DataLinks. + // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside. + if (colIdx === pillColIdx || colIdx === dataLinkColIdx) { + continue; + } + + const cell = await getCell(page, 1, colIdx); + await expect(cell.locator('a')).toBeVisible(); + expect(cell.locator('a')).toHaveAttribute('href'); + expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu'); + } + + const headerContainer = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer); + + // add another data link. now we'll check that the multi-link popups work. + await addDataLink('Another test link', 'https://grafana.com/foo'); + + // loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip. + for (let colIdx = 0; colIdx < colCount; colIdx++) { + const cell = await getCell(page, 1, colIdx); + if (colIdx === infoColumnIdx) { + // the Info column should still have its single link. + expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu'); + continue; + } + + // - pills column currently does not support DataLinks. + // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside. + if (colIdx === pillColIdx || colIdx === dataLinkColIdx) { + continue; + } + + await cell.locator('a').click({ force: true }); + await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).toBeVisible(); + + await headerContainer.click(); // convenient just to click the header to close the tooltip. + await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).not.toBeVisible(); + } + + // add an Action to the whole table and check that the action button is added to the tooltip. + // TODO -- saving for another day. + }); + + test('Empty Table panel', async ({ gotoDashboardPage, selectors }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '2' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage) + ).toBeVisible(); + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink')) + ).not.toBeVisible(); + }); + } +); diff --git a/e2e-playwright/panels-suite/table-sparkline.spec.ts b/e2e-playwright/panels-suite/table-sparkline.spec.ts new file mode 100644 index 00000000000..16dc55f3df4 --- /dev/null +++ b/e2e-playwright/panels-suite/table-sparkline.spec.ts @@ -0,0 +1,29 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +test.use({ + viewport: { width: 1280, height: 1080 }, + featureToggles: { + tableNextGen: true, + }, +}); + +test.describe( + 'Panels test: Table - Sparkline', + { + tag: ['@panels'], + }, + () => { + test('Tests sparkline tables are successfully rendered', async ({ gotoDashboardPage, selectors, page }) => { + await gotoDashboardPage({ + uid: 'd6373b49-1957-4f00-9218-ee2120d3ecd9', + queryParams: new URLSearchParams({ editPanel: '2' }), + }); + + await expect(page.getByRole('grid')).toBeVisible(); + + const uplotCount = await page.locator('.uplot').count(); + const rowCount = await page.getByRole('row').count(); + expect(uplotCount).toBe(rowCount - 1); + }); + } +); diff --git a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts index 4772a9717e8..577a758d97e 100644 --- a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts +++ b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts @@ -11,6 +11,12 @@ const STANDARD_OTIONS_CATEGORY = 'Standard options'; const DISPLAY_NAME_LABEL = 'Display name'; const REACT_TABLE_DASHBOARD = { uid: 'U_bZIMRMk' }; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + test.describe( 'plugin-e2e-api-tests admin', { @@ -53,7 +59,7 @@ test.describe( ).toHaveText(scenarios.map((s) => s.name)); }); - test('mocked query data response', async ({ panelEditPage, selectors }) => { + test('mocked query data response', async ({ panelEditPage, page }) => { await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200); await panelEditPage.datasource.set('gdev-testdata'); await panelEditPage.setVisualization(TABLE_VIZ_NAME); @@ -63,9 +69,9 @@ test.describe( formatExpectError('Did not expect panel error to be displayed after query execution') ).toBeHidden(); await expect( - panelEditPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.body), + page.getByRole('grid'), formatExpectError('Expected certain select options to be displayed after clicking on the select input') - ).toHaveText('val1val2val3val4'); + ).toHaveText(/val1val2val3val4/); }); }); diff --git a/e2e-playwright/start-and-run-suite b/e2e-playwright/start-server similarity index 60% rename from e2e-playwright/start-and-run-suite rename to e2e-playwright/start-server index 3322f449345..6685cd472d0 100755 --- a/e2e-playwright/start-and-run-suite +++ b/e2e-playwright/start-server @@ -15,9 +15,6 @@ fi if [ "$BASE_URL" != "" ]; then echo -e "BASE_URL set, skipping starting server" else - # Start it in the background - ./scripts/grafana-server/start-server $LICENSE_PATH 2>&1 > scripts/grafana-server/server.log & - ./scripts/grafana-server/wait-for-grafana + ./scripts/grafana-server/start-server $LICENSE_PATH > scripts/grafana-server/server.log fi -PORT=3001 HOST=localhost yarn playwright test diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json index d11250bdc4d..89d6c90be5d 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "private": true, "scripts": { "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", diff --git a/e2e-playwright/test-plugins/grafana-test-datasource/package.json b/e2e-playwright/test-plugins/grafana-test-datasource/package.json index 9f976341268..7ed88373c45 100644 --- a/e2e-playwright/test-plugins/grafana-test-datasource/package.json +++ b/e2e-playwright/test-plugins/grafana-test-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/grafana-e2etest-datasource", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "private": true, "scripts": { "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", diff --git a/e2e-playwright/various-suite/visualization-suggestions.spec.ts b/e2e-playwright/various-suite/visualization-suggestions.spec.ts index 964af821caa..38c6f30d298 100644 --- a/e2e-playwright/various-suite/visualization-suggestions.spec.ts +++ b/e2e-playwright/various-suite/visualization-suggestions.spec.ts @@ -1,5 +1,11 @@ import { test, expect } from '@grafana/plugin-e2e'; +test.use({ + featureToggles: { + tableNextGen: true, + }, +}); + test.describe( 'Visualization suggestions', { @@ -39,9 +45,7 @@ test.describe( await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Table')).click(); // Verify table header is visible - await expect( - panelEditPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Table.header) - ).toBeVisible(); + await expect(page.getByRole('grid').getByRole('row').first()).toBeVisible(); }); } ); diff --git a/e2e/dashboards-suite/dashboard-live-streaming.spec.ts b/e2e/dashboards-suite/dashboard-live-streaming.spec.ts index 089b3ec4beb..0623c8c30c7 100644 --- a/e2e/dashboards-suite/dashboard-live-streaming.spec.ts +++ b/e2e/dashboards-suite/dashboard-live-streaming.spec.ts @@ -9,7 +9,7 @@ describe.skip('Dashboard Live streaming support', () => { }); it('Should receive streaming data', () => { - e2e.flows.openDashboard({ uid: 'live-e2e-test' }); + e2e.flows.openDashboard({ uid: 'live-e2e-test', queryParams: { '__feature.tableNextGen': false } }); cy.wait(1000); e2e.components.Panels.Panel.title('Live').should('exist'); e2e.components.Panels.Visualization.Table.body().find('[role="row"]').should('have.length.at.least', 5); diff --git a/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts b/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts index b91ccf5c270..5798b669f6f 100644 --- a/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts +++ b/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts @@ -8,7 +8,7 @@ describe('Dashboard Live streaming support', () => { }); it('Should receive streaming data', () => { - e2e.flows.openDashboard({ uid: 'live-e2e-test' }); + e2e.flows.openDashboard({ uid: 'live-e2e-test', queryParams: { '__feature.tableNextGen': false } }); cy.wait(1000); e2e.components.Panels.Panel.title('Live').should('exist'); e2e.components.Panels.Visualization.Table.body().find('[role="row"]').should('have.length.at.least', 5); diff --git a/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts b/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts index f8aab30de2f..7c8d3d4f197 100644 --- a/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts +++ b/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts @@ -8,7 +8,7 @@ describe.skip('Geomap spatial operations', () => { }); it.skip('Tests location auto option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transform data').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -26,7 +26,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests location coords option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transform data').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -50,7 +50,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests geoshash field column appears in table view', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transform data').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -73,7 +73,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests location lookup option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transform data').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); diff --git a/e2e/old-arch/panels-suite/panelEdit_base.spec.ts b/e2e/old-arch/panels-suite/panelEdit_base.spec.ts index 744c41ed8ce..8691382f90f 100644 --- a/e2e/old-arch/panels-suite/panelEdit_base.spec.ts +++ b/e2e/old-arch/panels-suite/panelEdit_base.spec.ts @@ -11,7 +11,7 @@ describe('Panel edit tests', () => { cy.intercept({ pathname: '/api/ds/query', }).as('query'); - e2e.flows.openDashboard({ uid: 'TkZXxlNG3' }); + e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { '__feature.tableNextGen': false } }); cy.wait('@query'); e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST); diff --git a/e2e/old-arch/various-suite/visualization-suggestions.spec.ts b/e2e/old-arch/various-suite/visualization-suggestions.spec.ts index aef309ea452..c5dfb03ef93 100644 --- a/e2e/old-arch/various-suite/visualization-suggestions.spec.ts +++ b/e2e/old-arch/various-suite/visualization-suggestions.spec.ts @@ -6,7 +6,7 @@ describe('Visualization suggestions', () => { }); it('Should be shown and clickable', () => { - e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { editPanel: 9 } }); + e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { '__feature.tableNextGen': false, editPanel: 9 } }); // Try visualization suggestions e2e.components.PanelEditor.toggleVizPicker().click(); diff --git a/e2e/panels-suite/geomap-spatial-operations-transform.spec.ts b/e2e/panels-suite/geomap-spatial-operations-transform.spec.ts index 615e69996a4..4eb24162791 100644 --- a/e2e/panels-suite/geomap-spatial-operations-transform.spec.ts +++ b/e2e/panels-suite/geomap-spatial-operations-transform.spec.ts @@ -8,7 +8,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests location auto option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transformations').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -26,7 +26,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests location coords option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transformations').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -50,7 +50,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests geoshash field column appears in table view', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transformations').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); @@ -73,7 +73,7 @@ describe.skip('Geomap spatial operations', () => { }); it('Tests location lookup option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { editPanel: 1 } }); + e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); e2e.components.Tab.title('Transformations').should('be.visible').click(); e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); diff --git a/e2e/panels-suite/panelEdit_base.spec.ts b/e2e/panels-suite/panelEdit_base.spec.ts index 67717fb06b3..d48879e5a8d 100644 --- a/e2e/panels-suite/panelEdit_base.spec.ts +++ b/e2e/panels-suite/panelEdit_base.spec.ts @@ -13,7 +13,7 @@ describe('Panel edit tests', () => { cy.intercept({ pathname: '/api/ds/query', }).as('query'); - e2e.flows.openDashboard({ uid: 'TkZXxlNG3' }); + e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { '__feature.tableNextGen': false } }); cy.wait('@query'); e2e.components.Panels.Panel.title('Lines 500 data points') diff --git a/e2e/various-suite/visualization-suggestions.spec.ts b/e2e/various-suite/visualization-suggestions.spec.ts index aef309ea452..c5dfb03ef93 100644 --- a/e2e/various-suite/visualization-suggestions.spec.ts +++ b/e2e/various-suite/visualization-suggestions.spec.ts @@ -6,7 +6,7 @@ describe('Visualization suggestions', () => { }); it('Should be shown and clickable', () => { - e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { editPanel: 9 } }); + e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { '__feature.tableNextGen': false, editPanel: 9 } }); // Try visualization suggestions e2e.components.PanelEditor.toggleVizPicker().click(); diff --git a/eslint.config.js b/eslint.config.js index d5bd122f12c..c31f2f77930 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -311,6 +311,7 @@ module.exports = [ files: [ 'public/app/!(plugins)/**/*.{ts,tsx,js,jsx}', 'packages/grafana-ui/**/*.{ts,tsx,js,jsx}', + 'packages/grafana-data/**/*.{ts,tsx,js,jsx}', 'packages/grafana-sql/**/*.{ts,tsx,js,jsx}', 'packages/grafana-prometheus/**/*.{ts,tsx,js,jsx}', ...pluginsToTranslate.map((plugin) => `${plugin}/**/*.{ts,tsx,js,jsx}`), @@ -409,4 +410,22 @@ module.exports = [ ], }, }, + { + name: 'grafana/no-extensions-imports', + files: ['**/*.{ts,tsx,js}'], + ignores: ['public/app/extensions/**/*'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['app/extensions', 'app/extensions/*'], + message: 'Importing from app/extensions is not allowed', + }, + ], + }, + ], + }, + }, ]; diff --git a/go.mod b/go.mod index b922613a0a8..46c47ef1f6e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana -go 1.24.4 +go 1.24.5 require ( buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1 // @grafana/observability-traces-and-profiling @@ -22,7 +22,7 @@ require ( github.com/BurntSushi/toml v1.5.0 // @grafana/identity-access-team github.com/DATA-DOG/go-sqlmock v1.5.2 // @grafana/grafana-search-and-storage github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group - github.com/Masterminds/semver/v3 v3.3.1 // @grafana/grafana-developer-enablement-squad + github.com/Masterminds/semver/v3 v3.4.0 // @grafana/grafana-developer-enablement-squad github.com/Masterminds/sprig/v3 v3.3.0 // @grafana/grafana-backend-group github.com/ProtonMail/go-crypto v1.1.6 // @grafana/plugins-platform-backend github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group @@ -85,7 +85,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.2 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250716142237-8308539caa27 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics @@ -95,16 +95,16 @@ require ( github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend - github.com/grafana/grafana-app-sdk v0.39.2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-app-sdk/logging v0.39.1 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.40.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk/logging v0.39.3 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.0.4 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // @grafana/partner-datasources - github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad + github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-google-sdk-go v0.4.1 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.278.0 // @grafana/plugins-platform-backend github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs - github.com/grafana/nanogit v0.0.0-20250709085038-55508a6a9f40 // @grafana-app-platform-squad + github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // @grafana-app-platform-squad github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling @@ -112,7 +112,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/grafana-search-and-storage github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // @grafana/grafana-backend-group - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // @grafana/identity-access-team + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // @grafana/identity-access-team github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad github.com/hashicorp/go-plugin v1.6.3 // @grafana/plugins-platform-backend @@ -155,7 +155,7 @@ require ( github.com/prometheus/alertmanager v0.28.0 // @grafana/alerting-backend github.com/prometheus/client_golang v1.22.0 // @grafana/alerting-backend github.com/prometheus/client_model v0.6.2 // @grafana/grafana-backend-group - github.com/prometheus/common v0.64.0 // @grafana/alerting-backend + github.com/prometheus/common v0.65.0 // @grafana/alerting-backend github.com/prometheus/prometheus v0.303.1 // @grafana/alerting-backend github.com/prometheus/sigv4 v0.1.2 // @grafana/alerting-backend github.com/redis/go-redis/v9 v9.7.3 // @grafana/alerting-backend @@ -165,7 +165,7 @@ require ( github.com/shopspring/decimal v1.4.0 // @grafana/grafana-datasources-core-services github.com/spf13/cobra v1.9.1 // @grafana/grafana-app-platform-squad github.com/spf13/pflag v1.0.6 // @grafana-app-platform-squad - github.com/spyzhov/ajson v0.9.6 // @grafana/grafana-backend-group + github.com/spyzhov/ajson v0.9.6 // @grafana/grafana-sharing-squad github.com/stretchr/testify v1.10.0 // @grafana/grafana-backend-group github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // @grafana/grafana-backend-group github.com/tjhop/slog-gokit v0.1.3 // @grafana/grafana-app-platform-squad @@ -181,15 +181,15 @@ require ( go.opentelemetry.io/collector/pdata v1.30.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // @grafana/plugins-platform-backend go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // @grafana/grafana-operator-experience-squad - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // @grafana/sharing-squad + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // @grafana/sharing-squad go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel v1.36.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.37.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.36.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.36.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.37.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/trace v1.37.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage go.uber.org/zap v1.27.0 // @grafana/identity-access-team @@ -200,7 +200,7 @@ require ( golang.org/x/mod v0.25.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.41.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.30.0 // @grafana/identity-access-team - golang.org/x/sync v0.15.0 // @grafana/alerting-backend + golang.org/x/sync v0.16.0 // @grafana/alerting-backend golang.org/x/text v0.26.0 // @grafana/grafana-backend-group golang.org/x/time v0.11.0 // @grafana/grafana-backend-group golang.org/x/tools v0.34.0 // indirect; @grafana/grafana-as-code @@ -212,15 +212,15 @@ require ( gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group gopkg.in/yaml.v2 v2.4.0 // @grafana/alerting-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend - k8s.io/api v0.33.2 // @grafana/grafana-app-platform-squad - k8s.io/apimachinery v0.33.2 // @grafana/grafana-app-platform-squad - k8s.io/apiserver v0.33.2 // @grafana/grafana-app-platform-squad - k8s.io/client-go v0.33.2 // @grafana/grafana-app-platform-squad - k8s.io/component-base v0.33.2 // @grafana/grafana-app-platform-squad + k8s.io/api v0.33.3 // @grafana/grafana-app-platform-squad + k8s.io/apimachinery v0.33.3 // @grafana/grafana-app-platform-squad + k8s.io/apiserver v0.33.3 // @grafana/grafana-app-platform-squad + k8s.io/client-go v0.33.3 // @grafana/grafana-app-platform-squad + k8s.io/component-base v0.33.3 // @grafana/grafana-app-platform-squad k8s.io/klog/v2 v2.130.1 // @grafana/grafana-app-platform-squad - k8s.io/kube-aggregator v0.33.1 // @grafana/grafana-app-platform-squad + k8s.io/kube-aggregator v0.33.3 // @grafana/grafana-app-platform-squad k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // @grafana/partner-datasources + k8s.io/utils v0.0.0-20241210054802-24370beab758 // @grafana/partner-datasources pgregory.net/rapid v1.2.0 // @grafana/grafana-operator-experience-squad sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // @grafana-app-platform-squad @@ -230,7 +230,7 @@ require ( require ( github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b // @grafana/alerting-backend - github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad + github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b // @grafana/identity-access-team github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b // @fcjack @matryer @@ -361,7 +361,7 @@ require ( github.com/dolthub/maphash v0.1.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect github.com/elazarl/goproxy v1.7.2 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emicklei/proto v1.13.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect @@ -372,7 +372,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gammazero/deque v0.2.1 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -448,7 +448,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/magefile/mage v1.15.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/matryer/is v1.4.1 // indirect github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect @@ -556,20 +556,21 @@ require ( go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.58.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect @@ -586,14 +587,14 @@ require ( gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/telebot.v3 v3.2.1 // indirect k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/kms v0.33.2 // indirect + k8s.io/kms v0.33.3 // indirect modernc.org/libc v1.65.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.10.0 // indirect modernc.org/sqlite v1.37.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream diff --git a/go.sum b/go.sum index f13cfc2dc32..65c00e3ac6b 100644 --- a/go.sum +++ b/go.sum @@ -736,8 +736,8 @@ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy86 github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= @@ -1131,8 +1131,8 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -1235,8 +1235,9 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -1573,8 +1574,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20250716142237-8308539caa27 h1:buoRKSKUO2QGJ16j2mY8T8S0KTjwB9uLtBrNvbYjkmM= -github.com/grafana/alerting v0.0.0-20250716142237-8308539caa27/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b h1:mfUAq/N+mS82EcE35hDXWtfVY7UhTjzZxzssvFt9tvQ= +github.com/grafana/alerting v0.0.0-20250725130805-615c8286e14b/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls= github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= @@ -1593,16 +1594,16 @@ github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= -github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmFFu/G98Xh8= -github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk v0.40.0 h1:KilbCFMYox2cnIi1W6ql7W+n9kms/NvWlBPM1m4Q4mg= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= -github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 h1:S4kHwr//AqhtL9xHBtz1gqVgZQeCRGTxjgsRBAkpjKY= -github.com/grafana/grafana-cloud-migration-snapshot v1.6.0/go.mod h1:rWNhyxYkgiXgV7xZ4yOQzMV08yikO8L8S8M5KNoQNpA= +github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI= +github.com/grafana/grafana-cloud-migration-snapshot v1.9.0/go.mod h1:nOHgq4Oa829qmBKA5KIXw5Ipo3rhLs0d6A8UI9Nw8Zk= github.com/grafana/grafana-google-sdk-go v0.4.1 h1:QdHmgDzlV3RzBTvIxd+WuxER1+afnFzUmEivbwDo27E= github.com/grafana/grafana-google-sdk-go v0.4.1/go.mod h1:U73+w9DlbEtUonhQUzERwlXnzWTtfRoyrtKH8d3VY40= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= @@ -1613,8 +1614,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b h1:8o github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:q+h3HbmqU/PposW6lq8cMle1v8vuyX1LCMrGzbabHxc= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b h1:jr+C3epmjhd5Yyob4P1Z/dPaW4LRTkU5UJLXsI4eaeM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:WpI7TCck4P2wKTO2WJLBRcfOWvUGvTdxYu3QqS3z7jM= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20 h1:2aeZOXOdJID5BbrUtBWG+yGUU4wDXkqwsq9drSalrDE= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716154214-974103c6fa20/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b h1:31MwoIKKT9Ay0ZjbT4lkfcPijiWogUWzXs2EjrCgodI= github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:dLtYBp1pza5HYalezNvzlP8JDeKrZ5BKTonDgEOE0NY= github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b h1:NV8v9xdM/pzjjy+1cLqUseia3bYcvQGh88vZdMW/jA0= @@ -1641,8 +1642,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 h1:ZYk42718k github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0= github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw= github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y= -github.com/grafana/nanogit v0.0.0-20250709085038-55508a6a9f40 h1:wsIgOI4Ou1o/UtxtJlemLufpVBpMdcXVJxedk0wLoCM= -github.com/grafana/nanogit v0.0.0-20250709085038-55508a6a9f40/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8= +github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 h1:oJnbhG6ZNy10AjsgNeAtAKeGHogIGOMfAsBH6fYYa5M= @@ -1674,8 +1675,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= @@ -1941,8 +1942,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -2233,8 +2235,8 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= @@ -2564,8 +2566,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0. go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= @@ -2573,59 +2575,59 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgY go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= -go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -2641,6 +2643,10 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s= go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw= @@ -2904,8 +2910,8 @@ golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3556,19 +3562,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= -k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= -k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -3576,17 +3582,17 @@ k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.33.2 h1:GFwNXX4CZGQCg9DPOaJi1/+iKidCtB9/OIAGdzRo8FI= -k8s.io/kms v0.33.2/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= -k8s.io/kube-aggregator v0.33.1 h1:PigQUqAvd6Y4hBjQAqhKz3lEJC2VHLL4bSOEuS06a40= -k8s.io/kube-aggregator v0.33.1/go.mod h1:16/wlU5Lj7hNJSv7JSu5FLvxyrgiJVLCHzfVoECAsuI= +k8s.io/kms v0.33.3 h1:7cQWC+GSH211NgY8LRKjBXNtkzra5SkpYzeZrOt5D+8= +k8s.io/kms v0.33.3/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= +k8s.io/kube-aggregator v0.33.3 h1:Pa6hQpKJMX0p0D2wwcxXJgu02++gYcGWXoW1z1ZJDfo= +k8s.io/kube-aggregator v0.33.3/go.mod h1:hwvkUoQ8q6gv0+SgNnlmQ3eUue1zHhJKTHsX7BwxwSE= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190722073852-5e22f3d471e6/go.mod h1:RZvgC8MSN6DjiMV6oIfEE9pDL9CYXokkfaCKZeHm3nc= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= @@ -3653,8 +3659,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= @@ -3663,7 +3669,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= diff --git a/go.work b/go.work index 8d1fff1ed6a..2735423d372 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.24.4 +go 1.24.5 // The `skip:golangci-lint` comment tag is used to exclude the package from the `golangci-lint` GitHub Action. // The module at the root of the repo (`.`) is excluded because ./pkg/... is included manually in the `golangci-lint` configuration. @@ -24,6 +24,6 @@ use ( ./pkg/semconv ) -replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56 diff --git a/go.work.sum b/go.work.sum index 45af5346b64..4c6414c6e58 100644 --- a/go.work.sum +++ b/go.work.sum @@ -20,6 +20,8 @@ cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbD cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go v0.118.1/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M= cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q= cloud.google.com/go/accessapproval v1.8.3 h1:axlU03FRiXDNupsmPG7LKzuS4Enk1gf598M62lWVB74= cloud.google.com/go/accessapproval v1.8.3/go.mod h1:3speETyAv63TDrDmo5lIkpVueFkQcQchkiw/TAMbBo4= cloud.google.com/go/accessapproval v1.8.6 h1:UkmDPCKvj24bkGVrvgJPcgSDkmIPw/bAmOiDb9avOiE= @@ -280,6 +282,7 @@ cloud.google.com/go/lifesciences v0.10.6/go.mod h1:1nnZwaZcBThDujs9wXzECnd1S5d+U cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/managedidentities v1.7.3 h1:b9xGs24BIjfyvLgCtJoClOZpPi8d8owPgWe5JEINgaY= cloud.google.com/go/managedidentities v1.7.3/go.mod h1:H9hO2aMkjlpY+CNnKWRh+WoQiUIDO8457wWzUGsdtLA= cloud.google.com/go/managedidentities v1.7.6 h1:zrZVWXZJlmHnfpyCrTQIbDBGUBHrcOOvrsjMjoXRxrk= @@ -302,6 +305,7 @@ cloud.google.com/go/metastore v1.14.7 h1:dLm59AHHZCorveCylj7c2iWhkQsmMIeWTsV+tG/ cloud.google.com/go/metastore v1.14.7/go.mod h1:0dka99KQofeUgdfu+K/Jk1KeT9veWZlxuZdJpZPtuYU= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= +cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/networkconnectivity v1.16.1 h1:YsVhG71ZC4FkqCP2oCI55x/JeGFyd7738Lt8iNTrzJw= cloud.google.com/go/networkconnectivity v1.16.1/go.mod h1:GBC1iOLkblcnhcnfRV92j4KzqGBrEI6tT7LP52nZCTk= cloud.google.com/go/networkconnectivity v1.17.1 h1:RQcG1rZNCNV5Dn3tnINs4TYswDXk2hKH+85eh+JvoWU= @@ -414,8 +418,6 @@ cloud.google.com/go/shell v1.8.3 h1:mjYgUsOtV3jl9xvDmcvlRRmA64deEPf52zOfuc68b/g= cloud.google.com/go/shell v1.8.3/go.mod h1:OYcrgWF6JSp/uk76sNTtYFlMD0ho2+Cdzc7U3P/bF54= cloud.google.com/go/shell v1.8.6 h1:jLWyztGlNWBx55QXBM4HbWvfv7aiRjPzRKTUkZA8dXk= cloud.google.com/go/shell v1.8.6/go.mod h1:GNbTWf1QA/eEtYa+kWSr+ef/XTCDkUzRpV3JPw0LqSk= -cloud.google.com/go/spanner v1.76.1 h1:vYbVZuXfnFwvNcvH3lhI2PeUA+kHyqKmLC7mJWaC4Ok= -cloud.google.com/go/spanner v1.76.1/go.mod h1:YtwoE+zObKY7+ZeDCBtZ2ukM+1/iPaMfUM+KnTh/sx0= cloud.google.com/go/spanner v1.82.0 h1:w9uO8RqEoBooBLX4nqV1RtgudyU2ZX780KTLRgeVg60= cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0= cloud.google.com/go/speech v1.26.0 h1:qvURtJs7BQzQhbxWxwai0pT79S8KLVKJ/4W8igVkt1Y= @@ -441,6 +443,7 @@ cloud.google.com/go/tpu v1.8.0 h1:BvMNijOb6Vd46Rr/SR5jWv1MPosOhVsi0UaeAGNjeds= cloud.google.com/go/tpu v1.8.0/go.mod h1:XyNzyK1xc55WvL5rZEML0Z9/TUHDfnq0uICkQw6rWMo= cloud.google.com/go/tpu v1.8.3 h1:S4Ptq+yFIPNLEzQ/OQwiIYDNzk5I2vYmhf0SmFQOmWo= cloud.google.com/go/tpu v1.8.3/go.mod h1:Do6Gq+/Jx6Xs3LcY2WhHyGwKDKVw++9jIJp+X+0rxRE= +cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= cloud.google.com/go/translate v1.12.3 h1:XJ7LipYJi80BCgVk2lx1fwc7DIYM6oV2qx1G4IAGQ5w= cloud.google.com/go/translate v1.12.3/go.mod h1:qINOVpgmgBnY4YTFHdfVO4nLrSBlpvlIyosqpGEgyEg= @@ -492,13 +495,17 @@ codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoP contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec h1:CSNP8nIEQt4sZEo2sGUiWSmVJ9c5QdyIQvwzZAsn+8Y= contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= +contrib.go.opencensus.io/exporter/zipkin v0.1.2 h1:YqE293IZrKtqPnpwDPH/lOqTWD/s3Iwabycam74JV3g= +contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60= contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06fa2la1/H/Ho= git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= @@ -540,8 +547,6 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbO github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6 h1:UucmvNRPE75F3KzT68GHhKzOPwttxiFkh1d5LTTywW8= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6/go.mod h1:XGripOBEUAcge8IUWR/NMAB5qO9k82tkbpoewBpyjYQ= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= @@ -558,6 +563,7 @@ github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/KimMachineGun/automemlimit v0.7.1 h1:QcG/0iCOLChjfUweIMC3YL5Xy9C3VBeNmCZHrZfJMBw= github.com/KimMachineGun/automemlimit v0.7.1/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -619,8 +625,6 @@ github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1 h1:nMp7diZObd4XEVUR0pEvn7/E13JI github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= -github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= -github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2 h1:vlYXbindmagyVA3RS2SPd47eKZ00GZZQcr+etTviHtc= @@ -674,10 +678,12 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= @@ -687,7 +693,6 @@ github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nC github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= @@ -751,9 +756,11 @@ github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= +github.com/dgryski/go-ddmin v0.0.0-20210904190556-96a6d69f1034/go.mod h1:zz4KxBkcXUWKjIcrc+uphJ1gPh/t18ymGm3PmQ+VGTk= github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b h1:Yqiad0+sloMPdd/0Fg22actpFx0dekpzt1xJmVNVkU0= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= @@ -802,8 +809,11 @@ github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQ github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/expr-lang/expr v1.17.0/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88= @@ -858,6 +868,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198 h1:FSii2UQeSLngl3jFoR4tUKZLprO7qUlh/TKKticc0BM= github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM= github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= @@ -874,12 +886,15 @@ github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= @@ -895,6 +910,7 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKG github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= @@ -919,6 +935,7 @@ github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 h1:H2p3XKDHnTBGkM github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945 h1:3imTbxFpZSVI6IBIB9mn+Xc40lUweWjfMaBSgXR7rLs= github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q= +github.com/grafana/alerting v0.0.0-20250709204613-c5c6f9c1653d/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= @@ -931,6 +948,7 @@ github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0j github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= @@ -947,6 +965,7 @@ github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec1 github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:VkX53kBiqIMHBoGgeEDJnzm5Nwcmv/726tuZuT5SvJY= github.com/grafana/grafana/apps/dashboard v0.0.0-20250616135341-59c2f154336b/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:eR8wca74ADgxBrvX0uNpdB1qnPaGx/KhCm4Xj8oqHfQ= github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI= github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA= github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA= @@ -969,8 +988,6 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -980,6 +997,7 @@ github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8Io github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= +github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -1003,6 +1021,8 @@ github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b h1:i44CesU68Z github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= github.com/influxdata/telegraf v1.16.3 h1:x0qeuSGGMg5y+YqP/5ZHwXZu3bcBrO8AAQOTNlYEb1c= github.com/influxdata/telegraf v1.16.3/go.mod h1:fX/6k7qpIqzVPWyeIamb0wN5hbwc0ANUaTS80lPYFB8= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= @@ -1090,6 +1110,7 @@ github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqA github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/matryer/moq v0.5.2 h1:b2bsanSaO6IdraaIvPBzHnqcrkkQmk1/310HdT2nNQs= github.com/matryer/moq v0.5.2/go.mod h1:W/k5PLfou4f+bzke9VPXTbfJljxoeR1tLHigsmbshmU= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2 h1:yVCLo4+ACVroOEr4iFU1iH46Ldlzz2rTuu18Ra7M8sU= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ= @@ -1097,6 +1118,7 @@ github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M= github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= @@ -1133,8 +1155,10 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.124.1 h1:+aiMrDR6xiaDM7xN4ByrBYI0Craqt68nZicmpYpt0co= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.124.1/go.mod h1:H/TEWN4jgExt0McrtrBK2VFK6r9LRsWtqhEZrH690rs= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1 h1:NrjsoVPxI6lmV8jPImDcMeqYh+97Y71f/HB5Sfpfe3I= @@ -1185,7 +1209,6 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2D github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= @@ -1202,6 +1225,7 @@ github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8 github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= github.com/prometheus/statsd_exporter v0.26.1 h1:ucbIAdPmwAUcA+dU+Opok8Qt81Aw8HanlO+2N/Wjv7w= github.com/prometheus/statsd_exporter v0.26.1/go.mod h1:XlDdjAmRmx3JVvPPYuFNUg+Ynyb5kR69iPPkQjxXFMk= @@ -1217,6 +1241,9 @@ github.com/richardartoul/molecule v1.0.0 h1:+LFA9cT7fn8KF39zy4dhOnwcOwRoqKiBkPqK github.com/richardartoul/molecule v1.0.0/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8= +github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY= @@ -1241,6 +1268,7 @@ github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -1271,6 +1299,8 @@ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYN github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= +github.com/tsenart/vegeta/v12 v12.12.0 h1:FKMMNomd3auAElO/TtbXzRFXAKGee6N/GKCGweFVm2U= +github.com/tsenart/vegeta/v12 v12.12.0/go.mod h1:gpdfR++WHV9/RZh4oux0f6lNPhsOH8pCjIGUlcPQe1M= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= @@ -1342,6 +1372,7 @@ go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWb go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opentelemetry.io/collector v0.124.0 h1:g/dfdGFhBcQI0ggGxTmGlJnJ6Yl6T2gVxQoIj4UfXCc= go.opentelemetry.io/collector v0.124.0/go.mod h1:QzERYfmHUedawjr8Ph/CBEEkVqWS8IlxRLAZt+KHlCg= go.opentelemetry.io/collector/client v1.30.0 h1:QbvOrvwUGcnVjnIBn2zyLLubisOjgh7kMgkzDAiYpHg= @@ -1468,6 +1499,9 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 h1:ZIt0ya9/y4WyRIzfLC8hQRRsWg0J9M9GyaGtIMiElZI= +go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0/go.mod h1:F1aJ9VuiKWOlWwKdTYDUp1aoS0HzQxg38/VLxKmhm5U= go.opentelemetry.io/contrib/otelconf v0.15.0 h1:BLNiIUsrNcqhSKpsa6CnhE6LdrpY1A8X0szMVsu99eo= go.opentelemetry.io/contrib/otelconf v0.15.0/go.mod h1:OPH1seO5z9dp1P26gnLtoM9ht7JDvh3Ws6XRHuXqImY= go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ= @@ -1481,39 +1515,32 @@ go.opentelemetry.io/otel/bridge/opencensus v1.35.0 h1:4nJfffRbozhqnuukfRkiahA94m go.opentelemetry.io/otel/bridge/opencensus v1.35.0/go.mod h1:359S30saRYNsB4A46EDx91SpXsQFNgkma7ftg2/L5/M= go.opentelemetry.io/otel/bridge/opentracing v1.35.0 h1:qT4jl1fYl0hHuRopNcwS94QosLFhGYcS0HacPUeXmT4= go.opentelemetry.io/otel/bridge/opentracing v1.35.0/go.mod h1:p5CbIL4v7uQz7mnQD6T/AZc1pPUzwz+2wZ1zrGY9Kgs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= @@ -1522,6 +1549,7 @@ golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbV golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= @@ -1529,13 +1557,19 @@ golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= @@ -1543,22 +1577,33 @@ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= @@ -1582,6 +1627,7 @@ google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqt google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= @@ -1626,14 +1672,17 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -1654,12 +1703,19 @@ k8s.io/code-generator v0.33.1 h1:ZLzIRdMsh3Myfnx9BaooX6iQry29UJjVfVG+BuS+UMw= k8s.io/code-generator v0.33.1/go.mod h1:HUKT7Ubp6bOgIbbaPIs9lpd2Q02uqkMCMx9/GjDrWpY= k8s.io/code-generator v0.33.2 h1:PCJ0Y6viTCxxJHMOyGqYwWEteM4q6y1Hqo2rNpl6jF4= k8s.io/code-generator v0.33.2/go.mod h1:hBjCA9kPMpjLWwxcr75ReaQfFXY8u+9bEJJ7kRw3J8c= +k8s.io/code-generator v0.33.3 h1:6+34LhYkIuQ/yn/E3qlpVqjQaP8smzCu4NE1A8b0LWs= +k8s.io/code-generator v0.33.3/go.mod h1:6Y02+HQJYgNphv9z3wJB5w+sjYDIEBQW7sh62PkufvA= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-aggregator v0.33.1/go.mod h1:16/wlU5Lj7hNJSv7JSu5FLvxyrgiJVLCHzfVoECAsuI= +k8s.io/kube-aggregator v0.33.2/go.mod h1:qQbliLwcdmx7/8mtvkc/9QV/ON2M6ZBMcffEUmrqKFw= +knative.dev/hack v0.0.0-20250514121446-f525e187efdc h1:8HmclJlA0zNE/G1SkgdC3/IFSSyhaSz2iIhihU6YbEo= +knative.dev/hack v0.0.0-20250514121446-f525e187efdc/go.mod h1:R0ritgYtjLDO9527h5vb5X6gfvt5LCrJ55BNbVDsWiY= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= diff --git a/hack/go.mod b/hack/go.mod index eb429b002ec..b1685be7028 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/hack -go 1.24.4 +go 1.24.5 require k8s.io/code-generator v0.33.1 diff --git a/lerna.json b/lerna.json index 71b3ceeef8f..fe2cdbf0402 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "12.1.0-pre" + "version": "12.2.0-pre" } diff --git a/package.json b/package.json index 202f700962a..4b25b18298f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "repository": "github:grafana/grafana", "scripts": { "predev": "./scripts/check-frontend-dev.sh", @@ -24,13 +24,13 @@ "e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev", "e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug", "e2e:playwright": "yarn playwright test", - "e2e:playwright:server": "yarn e2e:plugin:build && ./e2e-playwright/start-and-run-suite", "e2e:playwright:storybook": "yarn playwright test -c playwright.storybook.config.ts", "e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true", "e2e:plugin:build": "nx run-many -t build --projects='@test-plugins/*'", "e2e:plugin:build:dev": "nx run-many -t dev --projects='@test-plugins/*' --maxParallel=100", "test": "jest --notify --watch", "test:coverage": "jest --coverage", + "test:storybook": "yarn workspace @grafana/ui storybook:test", "test:coverage:changes": "jest --coverage --changedSince=origin/main", "test:accessibility-report": "./scripts/generate-a11y-report.sh", "test:ci": "mkdir -p reports/junit && JEST_JUNIT_OUTPUT_DIR=reports/junit jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%} --shard=${TEST_SHARD:-1}/${TEST_SHARD_TOTAL:-1}", @@ -47,7 +47,7 @@ "prettier:check": "prettier --check --ignore-path .prettierignore --list-different=false --log-level=warn \"**/*.{ts,tsx,scss,md,mdx,json,js,cjs}\"", "prettier:checkDocs": "prettier --check --list-different=false --log-level=warn \"docs/**/*.md\" \"*.md\" \"packages/**/*.{ts,tsx,scss,md,mdx,json,js,cjs}\"", "prettier:write": "prettier --ignore-path .prettierignore --list-different \"**/*.{js,ts,tsx,scss,md,mdx,json,cjs}\" --write", - "start": "yarn predev && NODE_ENV=dev nx exec -- webpack --config scripts/webpack/webpack.dev.js --watch", + "start": "yarn predev && NODE_ENV=dev nx exec -- webpack --config scripts/webpack/webpack.dev.js --watch $@", "start:liveReload": "yarn start -- --env liveReload=1", "start:noTsCheck": "yarn start -- --env noTsCheck=1", "start:noLint": "yarn start -- --env noTsCheck=1 --env noLint=1", @@ -80,9 +80,9 @@ }, "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", - "@babel/core": "7.26.10", - "@babel/preset-env": "7.26.9", - "@babel/runtime": "7.27.0", + "@babel/core": "7.28.0", + "@babel/preset-env": "7.28.0", + "@babel/runtime": "7.28.2", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", "@crowdin/crowdin-api-client": "^1.42.0", @@ -90,13 +90,13 @@ "@emotion/eslint-plugin": "11.12.0", "@grafana/eslint-config": "8.0.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", - "@grafana/plugin-e2e": "2.0.0", + "@grafana/plugin-e2e": "2.1.7", "@grafana/test-utils": "workspace:*", "@grafana/tsconfig": "^2.0.0", "@manypkg/get-packages": "^2.2.0", "@npmcli/package-json": "^5.2.0", - "@playwright/test": "1.52.0", - "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", + "@playwright/test": "1.54.1", + "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", "@react-types/button": "3.10.2", "@react-types/menu": "3.9.14", "@react-types/overlays": "3.8.12", @@ -109,7 +109,7 @@ "@swc/helpers": "0.5.15", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", @@ -135,7 +135,7 @@ "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", "@types/lucene": "^2", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0", "@types/pluralize": "^0.0.33", @@ -175,17 +175,17 @@ "codeowners": "^5.1.1", "confusing-browser-globals": "^1.0.11", "copy-webpack-plugin": "12.0.2", - "core-js": "3.40.0", + "core-js": "3.44.0", "crashme": "0.0.15", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.2", "cypress": "14.3.2", "cypress-file-upload": "5.0.8", "cypress-recurse": "^1.35.3", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "esbuild-loader": "4.3.0", "esbuild-plugin-browserslist": "^1.0.0", - "eslint": "9.19.0", + "eslint": "9.32.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "28.11.0", @@ -202,7 +202,7 @@ "eslint-webpack-plugin": "4.2.0", "expose-loader": "5.0.1", "fishery": "^2.2.2", - "fork-ts-checker-webpack-plugin": "9.0.2", + "fork-ts-checker-webpack-plugin": "9.1.0", "glob": "11.0.3", "html-loader": "5.1.0", "html-webpack-plugin": "5.6.3", @@ -233,19 +233,19 @@ "postcss-loader": "8.1.1", "postcss-reporter": "7.1.0", "postcss-scss": "4.0.9", - "prettier": "3.4.2", + "prettier": "3.6.2", "publint": "^0.3.12", "react-refresh": "0.14.0", "react-select-event": "5.5.1", "redux-mock-store": "1.5.5", "rimraf": "6.0.1", "sass": "1.83.4", - "sass-loader": "16.0.4", + "sass-loader": "16.0.5", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", "stylelint": "16.14.1", "stylelint-config-sass-guidelines": "12.1.0", - "terser-webpack-plugin": "5.3.11", + "terser-webpack-plugin": "5.3.14", "testing-library-selector": "0.3.1", "tracelib": "1.0.1", "ts-jest": "29.2.5", @@ -254,9 +254,8 @@ "webpack": "5.97.1", "webpack-assets-manifest": "^5.1.0", "webpack-cli": "6.0.1", - "webpack-dev-server": "5.2.1", "webpack-livereload-plugin": "3.0.2", - "webpack-manifest-plugin": "5.0.0", + "webpack-manifest-plugin": "5.0.1", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "^5.2.0-rc.1", "webpackbar": "^7.0.0", @@ -269,10 +268,11 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.27.13", + "@floating-ui/react": "0.27.14", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", + "@grafana/assistant": "0.0.10", "@grafana/aws-sdk": "0.7.1", "@grafana/azure-sdk": "0.0.7", "@grafana/data": "workspace:*", @@ -325,7 +325,7 @@ "ansicolor": "2.0.3", "baron": "3.0.3", "brace": "0.11.1", - "centrifuge": "5.3.5", + "centrifuge": "5.4.0", "classnames": "2.5.1", "combokeys": "^3.0.0", "comlink": "4.4.2", @@ -361,7 +361,7 @@ "lru-cache": "11.1.0", "lru-memoize": "^1.1.0", "lucene": "^2.1.1", - "marked": "16.0.0", + "marked": "16.1.1", "memoize-one": "6.0.0", "micro-memoize": "^4.1.2", "ml-regression-polynomial": "^3.0.0", @@ -372,7 +372,7 @@ "moveable": "0.53.0", "nanoid": "^5.0.9", "node-forge": "^1.3.1", - "ol": "10.6.0", + "ol": "10.6.1", "ol-ext": "4.0.33", "pluralize": "^8.0.0", "prismjs": "1.30.0", @@ -427,15 +427,14 @@ "type-fest": "^4.18.2", "uplot": "1.6.32", "uuid": "11.1.0", - "vis-data": "^7.1.10", - "vis-network": "9.1.13", + "vis-data": "^8.0.0", + "vis-network": "10.0.1", "whatwg-fetch": "3.6.20" }, "resolutions": { "underscore": "1.13.7", "@types/slate": "0.47.11", - "semver@~7.0.0": "7.5.4", - "semver@7.3.4": "7.5.4", + "semver@npm:~7.3.5": "^7.3.5", "debug@npm:^0.7.2": "2.6.9", "debug@npm:^0.7.4": "2.6.9", "slate-dev-environment@^0.2.2": "patch:slate-dev-environment@npm:0.2.5#.yarn/patches/slate-dev-environment-npm-0.2.5-9aeb7da7b5.patch", @@ -464,7 +463,7 @@ }, "packageManager": "yarn@4.9.2", "dependenciesMeta": { - "prettier@3.4.2": { + "prettier@3.6.2": { "unplugged": true } }, diff --git a/packages/README.md b/packages/README.md index b3797e69514..525d77097fb 100644 --- a/packages/README.md +++ b/packages/README.md @@ -53,7 +53,6 @@ Every commit to main that has changes within the `packages` directory is a subje 3. Run `yarn packages:build` script that compiles distribution code in `packages/grafana-*/dist`. 4. Run `yarn packages:pack` script to compress each package into `npm-artifacts/*.tgz` files. This is required for yarn to replace properties in the package.json files declared in the `publishConfig` property. 5. Depending on whether or not it's a prerelease: - - When releasing a prerelease run `./scripts/publish-npm-packages.sh --dist-tag 'next' --registry 'https://registry.npmjs.org/'` to publish new versions. - When releasing a stable version run `./scripts/publish-npm-packages.sh --dist-tag 'latest' --registry 'https://registry.npmjs.org/'` to publish new versions. - When releasing a test version run `./scripts/publish-npm-packages.sh --dist-tag 'test' --registry 'https://registry.npmjs.org/'` to publish test versions. diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index 32ab506a757..0d4dfa1c746 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/alerting", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Alerting Library – Build vertical integrations on top of the industry-leading alerting solution", "keywords": [ "typescript", @@ -75,7 +75,7 @@ "react-redux": "^9.2.0", "rimraf": "^6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "type-fest": "^4.40.0", "typescript": "5.8.3" diff --git a/packages/grafana-alerting/rollup.config.ts b/packages/grafana-alerting/rollup.config.ts index bd1384bbe21..1d9b0e1ae40 100644 --- a/packages/grafana-alerting/rollup.config.ts +++ b/packages/grafana-alerting/rollup.config.ts @@ -10,10 +10,12 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-alerting')], + treeshake: false, }, { input: 'src/unstable.ts', plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-alerting')], + treeshake: false, }, ]; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx index 11033d82739..46f3f72ed6f 100644 --- a/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx +++ b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx @@ -14,6 +14,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 64a027650c9..99cf6ee0f52 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -56,7 +56,8 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/schema": "12.1.0-pre", + "@grafana/i18n": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", "@leeoniya/ufuzzy": "1.0.18", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", @@ -68,11 +69,11 @@ "fast_array_intersect": "1.1.0", "history": "4.10.1", "lodash": "4.17.21", - "marked": "16.0.0", + "marked": "16.1.1", "marked-mangle": "1.1.11", "moment": "2.30.1", "moment-timezone": "0.5.47", - "ol": "10.6.0", + "ol": "10.6.1", "papaparse": "5.5.3", "react-use": "17.6.0", "rxjs": "7.8.2", @@ -87,17 +88,17 @@ "@rollup/plugin-node-resolve": "16.0.1", "@types/history": "4.7.11", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/papaparse": "5.3.16", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/tinycolor2": "1.4.6", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "react": "18.3.1", "react-dom": "18.3.1", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "typescript": "5.8.3" }, diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index 98f8341f6de..378ab225769 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -10,10 +10,12 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], + treeshake: false, }, { input: 'src/unstable.ts', plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], + treeshake: false, }, ]; diff --git a/packages/grafana-data/src/dataframe/processDataFrame.test.ts b/packages/grafana-data/src/dataframe/processDataFrame.test.ts index 7fab4d0bfb2..72b3041d87c 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.test.ts @@ -388,6 +388,59 @@ describe('sorted DataFrame', () => { }); }); +describe('sorted DataFrame by nanos', () => { + it('Should sort nanos with numeric timestamp', () => { + const frame = toDataFrame({ + fields: [ + { name: 'first', type: FieldType.time, values: [1, 1, 2, 2, 3, 3], nanos: [100, 102, 1, 2, 1000, 999] }, + { name: 'second', type: FieldType.string, values: ['a', 'b', 'c', 'd', 'e', 'f'] }, + ], + }); + + const sorted = sortDataFrame(frame, 0, true); + expect(sorted.length).toEqual(6); + expect(sorted.fields[0].values).toEqual([3, 3, 2, 2, 1, 1]); + expect(sorted.fields[1].values).toEqual(['e', 'f', 'd', 'c', 'b', 'a']); + }); + + it('Should sort by nanos with dateTime timestamp', () => { + const frame = toDataFrame({ + fields: [ + { + name: 'first', + type: FieldType.time, + values: [dateTime(50), dateTime(50), dateTime(100)], + nanos: [1, 0, 100], + }, + { name: 'second', type: FieldType.string, values: ['a', 'b', 'c'] }, + ], + }); + + const sorted = sortDataFrame(frame, 0); + expect(sorted.length).toEqual(3); + expect(sorted.fields[0].values).toEqual([dateTime(50), dateTime(50), dateTime(100)]); + expect(sorted.fields[1].values).toEqual(['b', 'a', 'c']); + }); + + // Not sure if we expect nanos to exist on any field type besides time fields, but the schema allows it. + // Keep in mind if sorting by a non time field, the nanos is expected to present on the field that is being sorted + it('Should sort by nanos with string timestamp', () => { + const frame = toDataFrame({ + fields: [ + { name: 'fist', type: FieldType.time, values: [1, 2, 3, 3] }, + { name: 'second', type: FieldType.string, values: ['a', 'b', 'b', 'c'], nanos: [100, 0, 1, 0] }, + { name: 'third', type: FieldType.number, values: [2000, 3000, 1000] }, + ], + }); + + const sorted = sortDataFrame(frame, 1, true); + expect(sorted.length).toEqual(4); + expect(sorted.fields[0].values).toEqual([3, 2, 3, 1]); + expect(sorted.fields[0].nanos).toBeUndefined(); + expect(sorted.fields[1].values).toEqual(['c', 'b', 'b', 'a']); + }); +}); + describe('reverse DataFrame', () => { const frame = toDataFrame({ fields: [ diff --git a/packages/grafana-data/src/datetime/formatter.test.ts b/packages/grafana-data/src/datetime/formatter.test.ts index 18bea1cde78..7ec7da8adc5 100644 --- a/packages/grafana-data/src/datetime/formatter.test.ts +++ b/packages/grafana-data/src/datetime/formatter.test.ts @@ -1,5 +1,90 @@ +import { initRegionalFormatForTests } from '@grafana/i18n'; + +import * as featureToggles from '../utils/featureToggles'; + import { dateTimeFormat, dateTimeFormatTimeAgo, dateTimeFormatWithAbbrevation, timeZoneAbbrevation } from './formatter'; +// Default time zone ("browser") is set to Pacific/Easter in jest.config.js +const referenceDate = '2020-04-17T12:36:15.779Z'; + +describe('dateTimeFormat (regionalFormatPreference)', () => { + let mockGetFeatureToggle: jest.SpyInstance; + + beforeAll(() => { + initRegionalFormatForTests('en-AU'); + mockGetFeatureToggle = jest.spyOn(featureToggles, 'getFeatureToggle').mockImplementation((featureName) => { + return featureName === 'localeFormatPreference'; + }); + }); + + afterAll(() => { + mockGetFeatureToggle.mockRestore(); + }); + + it('formats dates in the browsers timezone by default', () => { + expect(dateTimeFormat(referenceDate)).toBe('17/04/2020, 6:36:15 am'); + }); + + it('formats dates in the browsers timezone by default when invalid time zone is set', () => { + const options = { timeZone: 'asdf123' }; + expect(dateTimeFormat(referenceDate, options)).toBe('17/04/2020, 6:36:15 am'); + }); + + it.each([ + ['UTC', '17/04/2020, 12:36:15 pm'], + ['Europe/Stockholm', '17/04/2020, 2:36:15 pm'], + ['Australia/Perth', '17/04/2020, 8:36:15 pm'], + ['Asia/Yakutsk', '17/04/2020, 9:36:15 pm'], + ['America/Panama', '17/04/2020, 7:36:15 am'], + ['America/Los_Angeles', '17/04/2020, 5:36:15 am'], + ['Africa/Djibouti', '17/04/2020, 3:36:15 pm'], + ['Europe/London', '17/04/2020, 1:36:15 pm'], + ['Europe/Berlin', '17/04/2020, 2:36:15 pm'], + ['Europe/Moscow', '17/04/2020, 3:36:15 pm'], + ['Europe/Madrid', '17/04/2020, 2:36:15 pm'], + ['America/New_York', '17/04/2020, 8:36:15 am'], + ['America/Chicago', '17/04/2020, 7:36:15 am'], + ['America/Denver', '17/04/2020, 6:36:15 am'], + ])('formats with the supplied time zone %s', (timeZone, expected) => { + expect(dateTimeFormat(referenceDate, { timeZone })).toBe(expected); + }); + + it('does not include seconds in the output if the time does not', () => { + expect(dateTimeFormat('2020-04-17T12:36:00.000Z')).toBe('17/04/2020, 6:36 am'); + }); + + it("includes milliseconds in the output when 'defaultWithMS' is set to true", () => { + expect(dateTimeFormat('2020-04-17T12:36:15.123Z', { defaultWithMS: true })).toBe('17/04/2020, 6:36:15.123 am'); + }); + + it.each([ + ['en-GB', '17/04/2020, 06:36:15'], + ['en-US', '4/17/2020, 6:36:15 AM'], + ['fr-FR', '17/04/2020 06:36:15'], + ['es-ES', '17/4/2020, 6:36:15'], + ['de-DE', '17.4.2020, 06:36:15'], + ['pt-BR', '17/04/2020, 06:36:15'], + ['zh-Hans', '2020/4/17 06:36:15'], + ['it-IT', '17/04/2020, 06:36:15'], + ['ja-JP', '2020/4/17 6:36:15'], + ['id-ID', '17/4/2020, 06.36.15'], + ['ko-KR', '2020. 4. 17. 오전 6:36:15'], + ['ru-RU', '17.04.2020, 06:36:15'], + ['cs-CZ', '17. 4. 2020 6:36:15'], + ['nl-NL', '17-4-2020, 06:36:15'], + ['hu-HU', '2020. 04. 17. 6:36:15'], + ['pt-PT', '17/04/2020, 06:36:15'], + ['pl-PL', '17.04.2020, 06:36:15'], + ['sv-SE', '2020-04-17 06:36:15'], + ['tr-TR', '17.04.2020 06:36:15'], + ['zh-Hant', '2020/4/17 上午6:36:15'], + ])('with locale %s', (locale, expected) => { + initRegionalFormatForTests(locale); + + expect(dateTimeFormat(referenceDate)).toBe(expected); + }); +}); + describe('dateTimeFormat', () => { describe('when no time zone have been set', () => { const browserTime = dateTimeFormat(1587126975779, { timeZone: 'browser' }); @@ -81,7 +166,7 @@ describe('dateTimeFormat', () => { }); }); - describe('DateTimeFormatISO', () => { + describe('with custom ISO format', () => { it('should format according to ISO standard', () => { const options = { timeZone: 'Europe/Stockholm', format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }; expect(dateTimeFormat(1587126975779, options)).toBe('2020-04-17T14:36:15.779+02:00'); @@ -95,39 +180,41 @@ describe('dateTimeFormat', () => { expect(dateTimeFormat(1587126975779, options)).toBe('2020-04-17T14:36:15.779+02:00'); }); }); +}); - describe('dateTimeFormatTimeAgo', () => { - it('should return the correct format for years ago', () => { - const options = { timeZone: 'Europe/Stockholm' }; - expect(dateTimeFormatTimeAgo(1587126975779, options)).toContain('years ago'); - }); +describe('dateTimeFormatTimeAgo', () => { + it('should return the correct format for years ago', () => { + const options = { timeZone: 'Europe/Stockholm' }; + expect(dateTimeFormatTimeAgo(1587126975779, options)).toContain('years ago'); }); - describe('dateTimeFormatWithAbbreviation', () => { - it('should return the correct format with zone abbreviation', () => { - const options = { timeZone: 'Europe/Stockholm' }; - expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 14:36:15 CEST'); - }); - it('should return the correct format with zone abbreviation', () => { - const options = { timeZone: 'America/New_York' }; - expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 08:36:15 EDT'); - }); - it('should return the correct format with zone abbreviation', () => { - const options = { timeZone: 'Europe/Bucharest' }; - expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 15:36:15 EEST'); - }); +}); + +describe('dateTimeFormatWithAbbreviation', () => { + it('should return the correct format with zone abbreviation', () => { + const options = { timeZone: 'Europe/Stockholm' }; + expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 14:36:15 CEST'); }); - describe('timeZoneAbbrevation', () => { - it('should return the correct abbreviation', () => { - const options = { timeZone: 'Europe/Stockholm' }; - expect(timeZoneAbbrevation(1587126975779, options)).toBe('CEST'); - }); - it('should return the correct abbreviation', () => { - const options = { timeZone: 'America/New_York' }; - expect(timeZoneAbbrevation(1587126975779, options)).toBe('EDT'); - }); - it('should return the correct abbreviation', () => { - const options = { timeZone: 'Europe/Bucharest' }; - expect(timeZoneAbbrevation(1587126975779, options)).toBe('EEST'); - }); + it('should return the correct format with zone abbreviation', () => { + const options = { timeZone: 'America/New_York' }; + expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 08:36:15 EDT'); + }); + it('should return the correct format with zone abbreviation', () => { + const options = { timeZone: 'Europe/Bucharest' }; + expect(dateTimeFormatWithAbbrevation(1587126975779, options)).toBe('2020-04-17 15:36:15 EEST'); + }); +}); + +describe('timeZoneAbbrevation', () => { + it('should return the correct abbreviation', () => { + const options = { timeZone: 'Europe/Stockholm' }; + expect(timeZoneAbbrevation(1587126975779, options)).toBe('CEST'); + }); + it('should return the correct abbreviation', () => { + const options = { timeZone: 'America/New_York' }; + expect(timeZoneAbbrevation(1587126975779, options)).toBe('EDT'); + }); + it('should return the correct abbreviation', () => { + const options = { timeZone: 'Europe/Bucharest' }; + expect(timeZoneAbbrevation(1587126975779, options)).toBe('EEST'); }); }); diff --git a/packages/grafana-data/src/datetime/formatter.ts b/packages/grafana-data/src/datetime/formatter.ts index 7facb730195..cc90e27491a 100644 --- a/packages/grafana-data/src/datetime/formatter.ts +++ b/packages/grafana-data/src/datetime/formatter.ts @@ -1,12 +1,77 @@ /* eslint-disable id-blacklist, no-restricted-imports */ import moment, { Moment } from 'moment-timezone'; +import { formatDate } from '@grafana/i18n'; + import { TimeZone } from '../types/time'; +import { getFeatureToggle } from '../utils/featureToggles'; import { DateTimeOptions, getTimeZone } from './common'; import { systemDateFormats } from './formats'; import { DateTimeInput, toUtc, dateTimeAsMoment } from './moment_wrapper'; +/** + * Converts a Grafana DateTimeInput to a plain Javascript Date object. + */ +function toDate(dateInUtc: DateTimeInput): Date { + if (dateInUtc instanceof Date) { + return dateInUtc; + } + + if (typeof dateInUtc === 'string' || typeof dateInUtc === 'number') { + return new Date(dateInUtc); + } + + return dateTimeAsMoment(dateInUtc).toDate(); +} + +/** + * Converts a Grafana timezone string to an IANA timezone string. + */ +export function toIANATimezone(grafanaTimezone: string) { + // Intl APIs will use the browser's timezone by default (if tz is undefined) + if (grafanaTimezone === 'browser') { + return undefined; + } + + const zone = moment.tz.zone(grafanaTimezone); + if (!zone) { + // If the timezone is invalid, we default to the browser's timezone + return undefined; + } + + return grafanaTimezone; +} + +function getIntlOptions( + date: Date, + options?: DateTimeOptionsWithFormat +): Intl.DateTimeFormatOptions & { timeZone?: string } { + const timeZone = getTimeZone(options); + + const intlOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', // ↔ dateStyle: 'short' + month: 'numeric', + day: 'numeric', + hour: 'numeric', // ↔ timeStyle: 'short' + minute: 'numeric', + timeZone: toIANATimezone(timeZone), + }; + + // If the time has seconds, ensure they're included in the format + const hasSeconds = date.getSeconds() !== 0; + if (hasSeconds) { + intlOptions.second = 'numeric'; + } + + if (options?.defaultWithMS) { + intlOptions.second = 'numeric'; + intlOptions.fractionalSecondDigits = 3; // Include milliseconds + } + + return intlOptions; +} + /** * The type describing the options that can be passed to the {@link dateTimeFormat} * helper function to control how the date and time value passed to the function is @@ -23,17 +88,30 @@ export interface DateTimeOptionsWithFormat extends DateTimeOptions { type DateTimeFormatter = (dateInUtc: DateTimeInput, options?: T) => string; +// NOTE: +// These date formatting functions now just wrap the @grafana/i18n formatting functions +// (which themselves wrap the browserIntl APIs). In the future we may deprecate these +// in favor of using @grafana/i18n directly. + /** - * Helper function to format date and time according to the specified options. If no options - * are supplied, then default values are used. For more details, see {@link DateTimeOptionsWithFormat}. + * Helper function to format date and time according to the specified options. + * If no options are supplied, then the date is formatting according to the user's locale preference. * * @param dateInUtc - date in UTC format, e.g. string formatted with UTC offset, UNIX epoch in seconds etc. * @param options * * @public */ -export const dateTimeFormat: DateTimeFormatter = (dateInUtc, options?) => - toTz(dateInUtc, getTimeZone(options)).format(getFormat(options)); +export const dateTimeFormat: DateTimeFormatter = (dateInUtc, options?) => { + // If a custom format is provided (or the toggle isn't enabled), use the previous implementation + if (!getFeatureToggle('localeFormatPreference') || options?.format) { + return toTz(dateInUtc, getTimeZone(options)).format(getFormat(options)); + } + + const dateAsDate = toDate(dateInUtc); + const intlOptions = getIntlOptions(dateAsDate, options); // TODO - if invalid timezone, use browser timezone + return formatDate(dateAsDate, intlOptions); +}; /** * Helper function to format date and time according to the standard ISO format e.g. 2013-02-04T22:44:30.652Z. @@ -70,8 +148,18 @@ export const dateTimeFormatTimeAgo: DateTimeFormatter = (dateInUtc, options?) => * * @public */ -export const dateTimeFormatWithAbbrevation: DateTimeFormatter = (dateInUtc, options?) => - toTz(dateInUtc, getTimeZone(options)).format(`${systemDateFormats.fullDate} z`); +export const dateTimeFormatWithAbbrevation: DateTimeFormatter = (dateInUtc, options?) => { + // If a custom format is provided (or the toggle isn't enabled), use the previous implementation + if (!getFeatureToggle('localeFormatPreference') || options?.format) { + return toTz(dateInUtc, getTimeZone(options)).format(`${systemDateFormats.fullDate} z`); + } + + const dateAsDate = toDate(dateInUtc); + const intlOptions = getIntlOptions(dateAsDate, options); + intlOptions.timeZoneName = 'short'; + + return formatDate(dateAsDate, intlOptions); +}; /** * Helper function to return only the time zone abbreviation for a given date and time value. If no options diff --git a/packages/grafana-data/src/datetime/moment_wrapper.ts b/packages/grafana-data/src/datetime/moment_wrapper.ts index 4b5bfc44290..0120e016d56 100644 --- a/packages/grafana-data/src/datetime/moment_wrapper.ts +++ b/packages/grafana-data/src/datetime/moment_wrapper.ts @@ -1,4 +1,5 @@ import moment, { Moment, MomentInput, DurationInputArg1, DurationInputArg2 } from 'moment'; +import { tz } from 'moment-timezone'; import { TimeZone } from '../types/time'; /* eslint-disable id-blacklist, no-restricted-imports */ @@ -130,9 +131,9 @@ export const dateTimeForTimeZone = ( let result: moment.Moment; if (typeof input === 'string' && formatInput) { - result = moment.tz(input, formatInput, timezone); + result = tz(input, formatInput, timezone); } else { - result = moment.tz(input, timezone); + result = tz(input, timezone); } if (isDateTime(result)) { diff --git a/packages/grafana-data/src/datetime/rangeutil.test.ts b/packages/grafana-data/src/datetime/rangeutil.test.ts index 684e4a11935..81ac6e2f6f8 100644 --- a/packages/grafana-data/src/datetime/rangeutil.test.ts +++ b/packages/grafana-data/src/datetime/rangeutil.test.ts @@ -1,9 +1,13 @@ +import { initRegionalFormatForTests } from '@grafana/i18n'; + import { RawTimeRange, TimeRange } from '../types/time'; +import * as featureToggles from '../utils/featureToggles'; import { dateTime } from './moment_wrapper'; import { convertRawToRange, describeInterval, + describeTimeRange, isRelativeTimeRange, relativeToTimeRange, roundInterval, @@ -310,4 +314,180 @@ describe('Range Utils', () => { expect(relativeTimeRange.to).toEqual(604800); }); }); + + describe('describeTimeRange', () => { + it.each([ + ['now-5m', 'now', 'Last 5 minutes'], + ['now-15m', 'now', 'Last 15 minutes'], + ['now-1h', 'now', 'Last 1 hour'], + ['now-24h', 'now', 'Last 24 hours'], + ['now-7d', 'now', 'Last 7 days'], + ['now-30d', 'now', 'Last 30 days'], + ['now/d', 'now/d', 'Today'], + ['now/d', 'now', 'Today so far'], + ['now/w', 'now/w', 'This week'], + ['now/M', 'now/M', 'This month'], + ['now/y', 'now/y', 'This year'], + ['now-1d/d', 'now-1d/d', 'Yesterday'], + ['now-1w/w', 'now-1w/w', 'Previous week'], + ['now-1M/M', 'now-1M/M', 'Previous month'], + ['now-1y/y', 'now-1y/y', 'Previous year'], + ['now/fQ', 'now/fQ', 'This fiscal quarter'], + ['now/fy', 'now/fy', 'This fiscal year'], + ['now-1Q/fQ', 'now-1Q/fQ', 'Previous fiscal quarter'], + ['now-1y/fy', 'now-1y/fy', 'Previous fiscal year'], + ])('should return display name "%s" for range from "%s" to "%s"', (from, to, expected) => { + expect(describeTimeRange({ from, to })).toBe(expected); + }); + + it('should prioritize custom quick ranges over standard ranges', () => { + const customRanges = [{ from: 'now-5m', to: 'now', display: 'Lightning round!' }]; + + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, customRanges)).toBe('Lightning round!'); + }); + + it('should format with the default timezone', () => { + // Tests default to Pacific/Easter + const from = dateTime('2023-01-15T10:30:00Z'); + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }); + expect(result).toBe('2023-01-15 05:30:00 to 2023-01-15 09:45:00'); + }); + + it('should respect timezone in datetime formatting', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }, 'Australia/Sydney'); + expect(result).toBe('2023-01-15 21:30:00 to 2023-01-16 01:45:00'); + }); + + it('should handle absolute from, relative to', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = 'now'; + + const result = describeTimeRange({ from, to }); + expect(result).toBe('2023-01-15 05:30:00 to a few seconds ago'); + }); + + it('should handle relative from, absolute to', () => { + const from = 'now-1h'; + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }); + expect(result).toBe('an hour ago to 2023-01-15 09:45:00'); + }); + + it('should handle invalid relative expressions', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = 'invalid-expression'; + + const result = describeTimeRange({ from, to }); + expect(result).toContain('Invalid date'); + }); + + it('should handle empty custom ranges array', () => { + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, [])).toBe('Last 5 minutes'); + }); + + it('should handle null custom ranges', () => { + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, undefined)).toBe('Last 5 minutes'); + }); + }); + + describe('describeTimeRange - localeFormatPreference enabled', () => { + let mockGetFeatureToggle: jest.SpyInstance; + + beforeAll(() => { + initRegionalFormatForTests('en-AU'); + mockGetFeatureToggle = jest.spyOn(featureToggles, 'getFeatureToggle').mockImplementation((featureName) => { + return featureName === 'localeFormatPreference'; + }); + }); + + afterAll(() => { + mockGetFeatureToggle.mockRestore(); + }); + + it.each([ + ['now-5m', 'now', 'Last 5 minutes'], + ['now-15m', 'now', 'Last 15 minutes'], + ['now-1h', 'now', 'Last 1 hour'], + ['now-7d', 'now', 'Last 7 days'], + ['now/d', 'now/d', 'Today'], + ['now/d', 'now', 'Today so far'], + ['now/w', 'now/w', 'This week'], + ['now-1d/d', 'now-1d/d', 'Yesterday'], + ['now-1w/w', 'now-1w/w', 'Previous week'], + ['now-1y/y', 'now-1y/y', 'Previous year'], + ['now/fQ', 'now/fQ', 'This fiscal quarter'], + ['now-1Q/fQ', 'now-1Q/fQ', 'Previous fiscal quarter'], + ])('should return display name "%s" for range from "%s" to "%s"', (from, to, expected) => { + expect(describeTimeRange({ from, to })).toBe(expected); + }); + + it('should prioritize custom quick ranges over standard ranges', () => { + const customRanges = [{ from: 'now-5m', to: 'now', display: 'Lightning round!' }]; + + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, customRanges)).toBe('Lightning round!'); + }); + + it('should format with the default timezone', () => { + // Tests default to Pacific/Easter + const from = dateTime('2023-01-15T10:30:00Z'); + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }); + expect(result).toBe('15/1/23, 5:30 – 9:45 am'); + }); + + it('should respect timezone in datetime formatting', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }, 'Australia/Sydney'); + expect(result).toBe('15/1/23, 9:30 pm – 16/1/23, 1:45 am'); + }); + + it('should include seconds in the output if the time has seconds', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = dateTime('2023-01-15T14:45:12Z'); + + const result = describeTimeRange({ from, to }); + expect(result).toBe('15/1/23, 5:30:00 am – 9:45:12 am'); + }); + + it('should handle absolute from, relative to', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = 'now'; + + const result = describeTimeRange({ from, to }); + expect(result).toBe('15/01/2023, 5:30 am to a few seconds ago'); + }); + + it('should handle relative from, absolute to', () => { + const from = 'now-1h'; + const to = dateTime('2023-01-15T14:45:00Z'); + + const result = describeTimeRange({ from, to }); + expect(result).toBe('an hour ago to 15/01/2023, 9:45 am'); + }); + + it('should handle invalid relative expressions', () => { + const from = dateTime('2023-01-15T10:30:00Z'); + const to = 'invalid-expression'; + + const result = describeTimeRange({ from, to }); + expect(result).toContain('Invalid date'); + }); + + it('should handle empty custom ranges array', () => { + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, [])).toBe('Last 5 minutes'); + }); + + it('should handle null custom ranges', () => { + expect(describeTimeRange({ from: 'now-5m', to: 'now' }, undefined, undefined)).toBe('Last 5 minutes'); + }); + }); }); diff --git a/packages/grafana-data/src/datetime/rangeutil.ts b/packages/grafana-data/src/datetime/rangeutil.ts index 2a93c282551..680ce22dcf0 100644 --- a/packages/grafana-data/src/datetime/rangeutil.ts +++ b/packages/grafana-data/src/datetime/rangeutil.ts @@ -1,7 +1,10 @@ +import { formatDateRange } from '@grafana/i18n'; + import { RawTimeRange, TimeRange, TimeZone, IntervalValues, RelativeTimeRange, TimeOption } from '../types/time'; +import { getFeatureToggle } from '../utils/featureToggles'; import * as dateMath from './datemath'; -import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo } from './formatter'; +import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo, toIANATimezone } from './formatter'; import { isDateTime, DateTime, dateTime } from './moment_wrapper'; import { dateTimeParse } from './parser'; @@ -134,6 +137,17 @@ export function describeTextRange(expr: string): TimeOption { return opt; } +// TODO: Should we keep these format presets somewhere common? +const rangeFormatShort: Intl.DateTimeFormatOptions = { + dateStyle: 'short', + timeStyle: 'short', +}; + +const rangeFormatFull: Intl.DateTimeFormatOptions = { + dateStyle: 'short', + timeStyle: 'medium', +}; + /** * Use this function to get a properly formatted string representation of a {@link @grafana/data:RawTimeRange | range}. * @@ -154,9 +168,25 @@ export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone, quic const options = { timeZone }; if (isDateTime(range.from) && isDateTime(range.to)) { - return dateTimeFormat(range.from, options) + ' to ' + dateTimeFormat(range.to, options); + const fromDate = range.from.toDate(); + const toDate = range.to.toDate(); + + if (!getFeatureToggle('localeFormatPreference')) { + return dateTimeFormat(range.from, options) + ' to ' + dateTimeFormat(range.to, options); + } + + const hasSeconds = fromDate.getSeconds() !== 0 || toDate.getSeconds() !== 0; + const intlFormat = hasSeconds ? rangeFormatFull : rangeFormatShort; + const intlFormatOptions = { + ...intlFormat, + timeZone: timeZone ? toIANATimezone(timeZone) : undefined, + }; + + return formatDateRange(fromDate, toDate, intlFormatOptions); } + // TODO: We could update these to all use Intl APIs. + // Could we use formatRangeToParts and replace the 'other side' with the ago formatting? if (isDateTime(range.from)) { const parsed = dateMath.parse(range.to, true, 'utc'); return parsed ? dateTimeFormat(range.from, options) + ' to ' + dateTimeFormatTimeAgo(parsed, options) : ''; diff --git a/packages/grafana-data/src/field/fieldComparers.ts b/packages/grafana-data/src/field/fieldComparers.ts index 94bb4312b14..7b3e64bbc6e 100644 --- a/packages/grafana-data/src/field/fieldComparers.ts +++ b/packages/grafana-data/src/field/fieldComparers.ts @@ -17,9 +17,9 @@ export const fieldIndexComparer = (field: Field, reverse = false): IndexComparer return booleanIndexComparer(values, reverse); case FieldType.time: if (typeof field.values[0] === 'number') { - return timestampIndexComparer(values, reverse); + return timestampIndexComparer(values, reverse, field.nanos); } - return timeIndexComparer(values, reverse); + return timeIndexComparer(values, reverse, field.nanos); default: return naturalIndexComparer(reverse); } @@ -79,17 +79,24 @@ const falsyComparer = (a: unknown, b: unknown): number => { return 0; }; -const timestampIndexComparer = (values: number[], reverse: boolean): IndexComparer => { +const timestampIndexComparer = (values: number[], reverse: boolean, nanos?: number[]): IndexComparer => { let mult = reverse ? -1 : 1; + + if (nanos !== undefined) { + return (a: number, b: number): number => mult * (values[a] - values[b] || nanos[a] - nanos[b]); + } + return (a: number, b: number): number => mult * (values[a] - values[b]); }; -const timeIndexComparer = (values: unknown[], reverse: boolean): IndexComparer => { - return (a: number, b: number): number => { - const vA = values[a]; - const vB = values[b]; - return reverse ? timeComparer(vB, vA) : timeComparer(vA, vB); - }; +const timeIndexComparer = (values: unknown[], reverse: boolean, nanos?: number[]): IndexComparer => { + const mult = reverse ? -1 : 1; + + if (nanos !== undefined) { + return (a: number, b: number): number => mult * (timeComparer(values[a], values[b]) || nanos[a] - nanos[b]); + } + + return (a: number, b: number): number => mult * timeComparer(values[a], values[b]); }; const booleanIndexComparer = (values: boolean[], reverse: boolean): IndexComparer => { diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 2fced788c3e..b767026c7a2 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -660,6 +660,7 @@ export { type QueryFixAction, type QueryHint, type MetricFindValue, + type FiltersApplicability, type DataSourceJsonData, type DataSourceSettings, type DataSourceInstanceSettings, diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 416855f5c95..16b0dfe79a1 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -309,7 +309,7 @@ abstract class DataSourceApi< /** * Verify adhoc filters applicability based on queries and current filters */ - getApplicableFilters?(options?: DataSourceGetTagKeysOptions): Promise; + getFiltersApplicability?(options?: DataSourceGetTagKeysOptions): Promise; /** * Get tag keys for adhoc filters @@ -643,6 +643,16 @@ export interface MetricFindValue { expandable?: boolean; } +export interface FiltersApplicability { + key: string; + applicable: boolean; + // message explaining why the filter is not applicable + reason?: string; + // needed to differentiate between filters with same key + // but different origin + origin?: string; +} + export interface DataSourceJsonData { authType?: string; defaultRegion?: string; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 1f42c837828..c82fffa8ea5 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -166,11 +166,6 @@ export interface FeatureToggles { */ pluginsFrontendSandbox?: boolean; /** - * Enables right panel for the plugins details page - * @default true - */ - pluginsDetailsRightPanel?: boolean; - /** * Enables previous SQL data source dataset dropdown behavior */ sqlDatasourceDatabaseSelection?: boolean; @@ -257,11 +252,6 @@ export interface FeatureToggles { */ lokiRunQueriesInParallel?: boolean; /** - * Allow core plugins to be loaded as external - * @default true - */ - externalCorePlugins?: boolean; - /** * Automatic service account and token setup for plugins */ externalServiceAccounts?: boolean; @@ -292,6 +282,10 @@ export interface FeatureToggles { */ kubernetesLibraryPanels?: boolean; /** + * Routes library panel connections requests from /api to using search + */ + kubernetesLibraryPanelConnections?: boolean; + /** * Use the kubernetes API in the frontend for dashboards */ kubernetesDashboards?: boolean; @@ -354,6 +348,10 @@ export interface FeatureToggles { */ alertmanagerRemoteSecondary?: boolean; /** + * Enables a feature to avoid issues with concurrent writes to the alerting provenance table in MySQL + */ + alertingProvenanceLockWrites?: boolean; + /** * Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager. */ alertmanagerRemotePrimary?: boolean; @@ -398,6 +396,10 @@ export interface FeatureToggles { */ canvasPanelPanZoom?: boolean; /** + * Enables time comparison option in supported panels + */ + timeComparison?: boolean; + /** * Enables infinite scrolling for the Logs panel in Explore and Dashboards * @default true */ @@ -474,6 +476,10 @@ export interface FeatureToggles { */ sqlExpressions?: boolean; /** + * Enables column autocomplete for SQL Expressions + */ + sqlExpressionsColumnAutoComplete?: boolean; + /** * Enables the group to nested table transformation * @default true */ @@ -749,6 +755,11 @@ export interface FeatureToggles { */ alertingAIGenAlertRules?: boolean; /** + * Enable AI-generated feedback from the Grafana UI. + * @default false + */ + alertingAIFeedback?: boolean; + /** * Enable AI-improve alert rules labels and annotations. * @default false */ @@ -848,10 +859,6 @@ export interface FeatureToggles { */ grafanaconThemes?: boolean; /** - * Loads plugins from CDN synchronously - */ - pluginsCDNSyncLoader?: boolean; - /** * Enables the new Jira integration for contact points in cloud alert managers. */ alertingJiraIntegration?: boolean; @@ -988,6 +995,10 @@ export interface FeatureToggles { */ kubernetesAuthzApis?: boolean; /** + * Enables create, delete, and update mutations for resources owned by IAM identity + */ + kubernetesAuthnMutation?: boolean; + /** * Enables restore deleted dashboards feature * @default false */ @@ -1055,4 +1066,17 @@ export interface FeatureToggles { * @default false */ pluginAssetProvider?: boolean; + /** + * Enable dual reader for unified storage search + */ + unifiedStorageSearchDualReaderEnabled?: boolean; + /** + * Enables adhoc filtering support for the dashboard datasource + */ + dashboardDsAdHocFiltering?: boolean; + /** + * Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications. + * @default false + */ + alertmanagerRemoteSecondaryWithRemoteState?: boolean; } diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 79df07aae5a..0cb4a61a25b 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -274,6 +274,8 @@ export const availableIconsIndex = { attach: true, 'dollar-alt': true, 'ai-sparkle': true, + bitbucket: true, + git: true, }; export type IconName = keyof typeof availableIconsIndex; diff --git a/packages/grafana-data/src/utils/featureToggles.ts b/packages/grafana-data/src/utils/featureToggles.ts new file mode 100644 index 00000000000..125c8fa8536 --- /dev/null +++ b/packages/grafana-data/src/utils/featureToggles.ts @@ -0,0 +1,13 @@ +import { FeatureToggles } from '../types/featureToggles.gen'; + +type FeatureToggleName = keyof FeatureToggles; + +/** + * Check a featureToggle + * @param featureName featureToggle name + * @param def default value if featureToggles aren't defined, false if not provided + * @returns featureToggle value or def. + */ +export function getFeatureToggle(featureName: FeatureToggleName, def = false) { + return window.grafanaBootData?.settings.featureToggles[featureName] ?? def; +} diff --git a/packages/grafana-data/src/utils/namedColorsPalette.ts b/packages/grafana-data/src/utils/namedColorsPalette.ts index 6d798ec96af..ab47afbb115 100644 --- a/packages/grafana-data/src/utils/namedColorsPalette.ts +++ b/packages/grafana-data/src/utils/namedColorsPalette.ts @@ -1,3 +1,7 @@ +/** + * @deprecated + * This is defined on the theme object now. Use `theme.visualization.palette` instead. + */ export const classicColors = [ '#7EB26D', // 0: pale green '#EAB839', // 1: mustard diff --git a/packages/grafana-data/src/valueFormats/categories.ts b/packages/grafana-data/src/valueFormats/categories.ts index e1e1bdf51ba..6eb64037208 100644 --- a/packages/grafana-data/src/valueFormats/categories.ts +++ b/packages/grafana-data/src/valueFormats/categories.ts @@ -1,3 +1,5 @@ +import { t } from '@grafana/i18n'; + import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters'; import { dateTimeAsIso, @@ -36,408 +38,1546 @@ import { export const getCategories = (): ValueFormatCategory[] => [ { - name: 'Misc', + name: t('grafana-data.valueFormats.categories.misc.name', 'Misc'), formats: [ - { name: 'Number', id: 'none', fn: toFixedUnit('') }, - { name: 'String', id: 'string', fn: stringFormater }, { - name: 'short', + name: t('grafana-data.valueFormats.categories.misc.formats.name-number', 'Number'), + id: 'none', + fn: toFixedUnit(''), + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-string', 'String'), + id: 'string', + fn: stringFormater, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-short', 'short'), id: 'short', fn: scaledUnits(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Quadr', ' Quint', ' Sext', ' Sept']), }, - { name: 'SI short', id: 'sishort', fn: SIPrefix('') }, - { name: 'Percent (0-100)', id: 'percent', fn: toPercent }, - { name: 'Percent (0.0-1.0)', id: 'percentunit', fn: toPercentUnit }, - { name: 'Humidity (%H)', id: 'humidity', fn: toFixedUnit('%H') }, - { name: 'Decibel', id: 'dB', fn: toFixedUnit('dB') }, - { name: 'Candela (cd)', id: 'candela', fn: SIPrefix('cd') }, - { name: 'Hexadecimal (0x)', id: 'hex0x', fn: toHex0x }, - { name: 'Hexadecimal', id: 'hex', fn: toHex }, - { name: 'Scientific notation', id: 'sci', fn: sci }, - { name: 'Locale format', id: 'locale', fn: locale }, - { name: 'Pixels', id: 'pixel', fn: toFixedUnit('px') }, - ], - }, - { - name: 'Acceleration', - formats: [ - { name: 'Meters/sec²', id: 'accMS2', fn: toFixedUnit('m/sec²') }, - { name: 'Feet/sec²', id: 'accFS2', fn: toFixedUnit('f/sec²') }, - { name: 'G unit', id: 'accG', fn: toFixedUnit('g') }, - ], - }, - { - name: 'Angle', - formats: [ - { name: 'Degrees (°)', id: 'degree', fn: toFixedUnit('°') }, - { name: 'Radians', id: 'radian', fn: toFixedUnit('rad') }, - { name: 'Gradian', id: 'grad', fn: toFixedUnit('grad') }, - { name: 'Arc Minutes', id: 'arcmin', fn: toFixedUnit('arcmin') }, - { name: 'Arc Seconds', id: 'arcsec', fn: toFixedUnit('arcsec') }, - ], - }, - { - name: 'Area', - formats: [ - { name: 'Square Meters (m²)', id: 'areaM2', fn: toFixedUnit('m²') }, - { name: 'Square Feet (ft²)', id: 'areaF2', fn: toFixedUnit('ft²') }, - { name: 'Square Miles (mi²)', id: 'areaMI2', fn: toFixedUnit('mi²') }, - { name: 'Acres (ac)', id: 'acres', fn: toFixedUnit('ac') }, - { name: 'Hectares (ha)', id: 'hectares', fn: toFixedUnit('ha') }, - ], - }, - { - name: 'Computation', - formats: [ - { name: 'FLOP/s', id: 'flops', fn: SIPrefix('FLOPS') }, - { name: 'MFLOP/s', id: 'mflops', fn: SIPrefix('FLOPS', 2) }, - { name: 'GFLOP/s', id: 'gflops', fn: SIPrefix('FLOPS', 3) }, - { name: 'TFLOP/s', id: 'tflops', fn: SIPrefix('FLOPS', 4) }, - { name: 'PFLOP/s', id: 'pflops', fn: SIPrefix('FLOPS', 5) }, - { name: 'EFLOP/s', id: 'eflops', fn: SIPrefix('FLOPS', 6) }, - { name: 'ZFLOP/s', id: 'zflops', fn: SIPrefix('FLOPS', 7) }, - { name: 'YFLOP/s', id: 'yflops', fn: SIPrefix('FLOPS', 8) }, - ], - }, - { - name: 'Concentration', - formats: [ - { name: 'parts-per-million (ppm)', id: 'ppm', fn: toFixedUnit('ppm') }, - { name: 'parts-per-billion (ppb)', id: 'conppb', fn: toFixedUnit('ppb') }, - { name: 'nanogram per cubic meter (ng/m³)', id: 'conngm3', fn: toFixedUnit('ng/m³') }, - { name: 'nanogram per normal cubic meter (ng/Nm³)', id: 'conngNm3', fn: toFixedUnit('ng/Nm³') }, - { name: 'microgram per cubic meter (μg/m³)', id: 'conμgm3', fn: toFixedUnit('μg/m³') }, - { name: 'microgram per normal cubic meter (μg/Nm³)', id: 'conμgNm3', fn: toFixedUnit('μg/Nm³') }, - { name: 'milligram per cubic meter (mg/m³)', id: 'conmgm3', fn: toFixedUnit('mg/m³') }, - { name: 'milligram per normal cubic meter (mg/Nm³)', id: 'conmgNm3', fn: toFixedUnit('mg/Nm³') }, - { name: 'gram per cubic meter (g/m³)', id: 'congm3', fn: toFixedUnit('g/m³') }, - { name: 'gram per normal cubic meter (g/Nm³)', id: 'congNm3', fn: toFixedUnit('g/Nm³') }, - { name: 'milligrams per decilitre (mg/dL)', id: 'conmgdL', fn: toFixedUnit('mg/dL') }, - { name: 'millimoles per litre (mmol/L)', id: 'conmmolL', fn: toFixedUnit('mmol/L') }, - ], - }, - { - name: 'Currency', - formats: [ - { name: 'Dollars ($)', id: 'currencyUSD', fn: currency('$') }, - { name: 'Pounds (£)', id: 'currencyGBP', fn: currency('£') }, - { name: 'Euro (€)', id: 'currencyEUR', fn: currency('€') }, - { name: 'Yen (¥)', id: 'currencyJPY', fn: currency('¥') }, - { name: 'Rubles (₽)', id: 'currencyRUB', fn: currency('₽') }, - { name: 'Hryvnias (₴)', id: 'currencyUAH', fn: currency('₴') }, - { name: 'Real (R$)', id: 'currencyBRL', fn: currency('R$') }, - { name: 'Danish Krone (kr)', id: 'currencyDKK', fn: currency('kr', true) }, - { name: 'Icelandic Króna (kr)', id: 'currencyISK', fn: currency('kr', true) }, - { name: 'Norwegian Krone (kr)', id: 'currencyNOK', fn: currency('kr', true) }, - { name: 'Swedish Krona (kr)', id: 'currencySEK', fn: currency('kr', true) }, - { name: 'Czech koruna (czk)', id: 'currencyCZK', fn: currency('czk') }, - { name: 'Swiss franc (CHF)', id: 'currencyCHF', fn: currency('CHF') }, - { name: 'Polish Złoty (PLN)', id: 'currencyPLN', fn: currency('PLN') }, - { name: 'Bitcoin (฿)', id: 'currencyBTC', fn: currency('฿') }, - { name: 'Milli Bitcoin (฿)', id: 'currencymBTC', fn: currency('mBTC') }, - { name: 'Micro Bitcoin (฿)', id: 'currencyμBTC', fn: currency('μBTC') }, - { name: 'South African Rand (R)', id: 'currencyZAR', fn: currency('R') }, - { name: 'Indian Rupee (₹)', id: 'currencyINR', fn: currency('₹') }, - { name: 'South Korean Won (₩)', id: 'currencyKRW', fn: currency('₩') }, - { name: 'Indonesian Rupiah (Rp)', id: 'currencyIDR', fn: currency('Rp') }, - { name: 'Philippine Peso (PHP)', id: 'currencyPHP', fn: currency('PHP') }, - { name: 'Vietnamese Dong (VND)', id: 'currencyVND', fn: currency('đ', true) }, - { name: 'Turkish Lira (₺)', id: 'currencyTRY', fn: currency('₺', true) }, - { name: 'Malaysian Ringgit (RM)', id: 'currencyMYR', fn: currency('RM') }, - { name: 'CFP franc (XPF)', id: 'currencyXPF', fn: currency('XPF') }, - { name: 'Bulgarian Lev (BGN)', id: 'currencyBGN', fn: currency('BGN') }, - { name: 'Guaraní (₲)', id: 'currencyPYG', fn: currency('₲') }, - { name: 'Uruguay Peso (UYU)', id: 'currencyUYU', fn: currency('UYU') }, - { name: 'Israeli New Shekels (₪)', id: 'currencyILS', fn: currency('₪') }, - ], - }, - { - name: 'Data', - formats: [ - { name: 'bytes(IEC)', id: 'bytes', fn: binaryPrefix('B') }, - { name: 'bytes(SI)', id: 'decbytes', fn: SIPrefix('B') }, - { name: 'bits(IEC)', id: 'bits', fn: binaryPrefix('b') }, - { name: 'bits(SI)', id: 'decbits', fn: SIPrefix('b') }, - { name: 'kibibytes', id: 'kbytes', fn: binaryPrefix('B', 1) }, - { name: 'kilobytes', id: 'deckbytes', fn: SIPrefix('B', 1) }, - { name: 'mebibytes', id: 'mbytes', fn: binaryPrefix('B', 2) }, - { name: 'megabytes', id: 'decmbytes', fn: SIPrefix('B', 2) }, - { name: 'gibibytes', id: 'gbytes', fn: binaryPrefix('B', 3) }, - { name: 'gigabytes', id: 'decgbytes', fn: SIPrefix('B', 3) }, - { name: 'tebibytes', id: 'tbytes', fn: binaryPrefix('B', 4) }, - { name: 'terabytes', id: 'dectbytes', fn: SIPrefix('B', 4) }, - { name: 'pebibytes', id: 'pbytes', fn: binaryPrefix('B', 5) }, - { name: 'petabytes', id: 'decpbytes', fn: SIPrefix('B', 5) }, - ], - }, - { - name: 'Data rate', - formats: [ - { name: 'packets/sec', id: 'pps', fn: SIPrefix('p/s') }, - { name: 'bytes/sec(IEC)', id: 'binBps', fn: binaryPrefix('B/s') }, - { name: 'bytes/sec(SI)', id: 'Bps', fn: SIPrefix('B/s') }, - { name: 'bits/sec(IEC)', id: 'binbps', fn: binaryPrefix('b/s') }, - { name: 'bits/sec(SI)', id: 'bps', fn: SIPrefix('b/s') }, - { name: 'kibibytes/sec', id: 'KiBs', fn: binaryPrefix('B/s', 1) }, - { name: 'kibibits/sec', id: 'Kibits', fn: binaryPrefix('b/s', 1) }, - { name: 'kilobytes/sec', id: 'KBs', fn: SIPrefix('B/s', 1) }, - { name: 'kilobits/sec', id: 'Kbits', fn: SIPrefix('b/s', 1) }, - { name: 'mebibytes/sec', id: 'MiBs', fn: binaryPrefix('B/s', 2) }, - { name: 'mebibits/sec', id: 'Mibits', fn: binaryPrefix('b/s', 2) }, - { name: 'megabytes/sec', id: 'MBs', fn: SIPrefix('B/s', 2) }, - { name: 'megabits/sec', id: 'Mbits', fn: SIPrefix('b/s', 2) }, - { name: 'gibibytes/sec', id: 'GiBs', fn: binaryPrefix('B/s', 3) }, - { name: 'gibibits/sec', id: 'Gibits', fn: binaryPrefix('b/s', 3) }, - { name: 'gigabytes/sec', id: 'GBs', fn: SIPrefix('B/s', 3) }, - { name: 'gigabits/sec', id: 'Gbits', fn: SIPrefix('b/s', 3) }, - { name: 'tebibytes/sec', id: 'TiBs', fn: binaryPrefix('B/s', 4) }, - { name: 'tebibits/sec', id: 'Tibits', fn: binaryPrefix('b/s', 4) }, - { name: 'terabytes/sec', id: 'TBs', fn: SIPrefix('B/s', 4) }, - { name: 'terabits/sec', id: 'Tbits', fn: SIPrefix('b/s', 4) }, - { name: 'pebibytes/sec', id: 'PiBs', fn: binaryPrefix('B/s', 5) }, - { name: 'pebibits/sec', id: 'Pibits', fn: binaryPrefix('b/s', 5) }, - { name: 'petabytes/sec', id: 'PBs', fn: SIPrefix('B/s', 5) }, - { name: 'petabits/sec', id: 'Pbits', fn: SIPrefix('b/s', 5) }, - ], - }, - { - name: 'Date & time', - formats: [ - { name: 'Datetime ISO', id: 'dateTimeAsIso', fn: dateTimeAsIso }, - { name: 'Datetime ISO (No date if today)', id: 'dateTimeAsIsoNoDateIfToday', fn: dateTimeAsIsoNoDateIfToday }, - { name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS }, - { name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday }, - { name: 'Datetime local', id: 'dateTimeAsLocal', fn: getDateTimeAsLocalFormat() }, { - name: 'Datetime local (No date if today)', + name: t('grafana-data.valueFormats.categories.misc.formats.name-si-short', 'SI short'), + id: 'sishort', + fn: SIPrefix(''), + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-percent-100', 'Percent (0-100)'), + id: 'percent', + fn: toPercent, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-percent-1', 'Percent (0.0-1.0)'), + id: 'percentunit', + fn: toPercentUnit, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-humidity', 'Humidity (%H)'), + id: 'humidity', + fn: toFixedUnit('%H'), + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-decibel', 'Decibel'), + id: 'dB', + fn: toFixedUnit('dB'), + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-candala', 'Candela (cd)'), + id: 'candela', + fn: SIPrefix('cd'), + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-hexadecimal-0x', 'Hexadecimal (0x)'), + id: 'hex0x', + fn: toHex0x, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-hexadecimal', 'Hexadecimal'), + id: 'hex', + fn: toHex, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-scientific', 'Scientific notation'), + id: 'sci', + fn: sci, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-locale', 'Locale format'), + id: 'locale', + fn: locale, + }, + { + name: t('grafana-data.valueFormats.categories.misc.formats.name-pixels', 'Pixels'), + id: 'pixel', + fn: toFixedUnit('px'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.acceleration.name', 'Acceleration'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.acceleration.formats.name-meters-sec', 'Meters/sec²'), + id: 'accMS2', + fn: toFixedUnit('m/sec²'), + }, + { + name: t('grafana-data.valueFormats.categories.acceleration.formats.name-feet-sec', 'Feet/sec²'), + id: 'accFS2', + fn: toFixedUnit('f/sec²'), + }, + { + name: t('grafana-data.valueFormats.categories.acceleration.formats.name-g-unit', 'G unit'), + id: 'accG', + fn: toFixedUnit('g'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.angle.name', 'Angle'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.angle.formats.name-degrees', 'Degrees (°)'), + id: 'degree', + fn: toFixedUnit('°'), + }, + { + name: t('grafana-data.valueFormats.categories.angle.formats.name-radians', 'Radians'), + id: 'radian', + fn: toFixedUnit('rad'), + }, + { + name: t('grafana-data.valueFormats.categories.angle.formats.name-gradian', 'Gradian'), + id: 'grad', + fn: toFixedUnit('grad'), + }, + { + name: t('grafana-data.valueFormats.categories.angle.formats.name-arc-minutes', 'Arc Minutes'), + id: 'arcmin', + fn: toFixedUnit('arcmin'), + }, + { + name: t('grafana-data.valueFormats.categories.angle.formats.name-arc-seconds', 'Arc Seconds'), + id: 'arcsec', + fn: toFixedUnit('arcsec'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.area.name', 'Area'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.area.formats.name-square-meters', 'Square Meters (m²)'), + id: 'areaM2', + fn: toFixedUnit('m²'), + }, + { + name: t('grafana-data.valueFormats.categories.area.formats.name-square-feet', 'Square Feet (ft²)'), + id: 'areaF2', + fn: toFixedUnit('ft²'), + }, + { + name: t('grafana-data.valueFormats.categories.area.formats.name-square-miles', 'Square Miles (mi²)'), + id: 'areaMI2', + fn: toFixedUnit('mi²'), + }, + { + name: t('grafana-data.valueFormats.categories.area.formats.name-acres', 'Acres (ac)'), + id: 'acres', + fn: toFixedUnit('ac'), + }, + { + name: t('grafana-data.valueFormats.categories.area.formats.name-hectares', 'Hectares (ha)'), + id: 'hectares', + fn: toFixedUnit('ha'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.computation.name', 'Computation'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-flops', 'FLOP/s'), + id: 'flops', + fn: SIPrefix('FLOPS'), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-mflops', 'MFLOP/s'), + id: 'mflops', + fn: SIPrefix('FLOPS', 2), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-gflops', 'GFLOP/s'), + id: 'gflops', + fn: SIPrefix('FLOPS', 3), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-tflops', 'TFLOP/s'), + id: 'tflops', + fn: SIPrefix('FLOPS', 4), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-pflops', 'PFLOP/s'), + id: 'pflops', + fn: SIPrefix('FLOPS', 5), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-eflops', 'EFLOP/s'), + id: 'eflops', + fn: SIPrefix('FLOPS', 6), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-zflops', 'ZFLOP/s'), + id: 'zflops', + fn: SIPrefix('FLOPS', 7), + }, + { + name: t('grafana-data.valueFormats.categories.computation.formats.name-yflops', 'YFLOP/s'), + id: 'yflops', + fn: SIPrefix('FLOPS', 8), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.concentration.name', 'Concentration'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.concentration.formats.name-ppm', 'parts-per-million (ppm)'), + id: 'ppm', + fn: toFixedUnit('ppm'), + }, + { + name: t('grafana-data.valueFormats.categories.concentration.formats.name-ppb', 'parts-per-billion (ppb)'), + id: 'conppb', + fn: toFixedUnit('ppb'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-ng-m3', + 'nanogram per cubic meter (ng/m³)' + ), + id: 'conngm3', + fn: toFixedUnit('ng/m³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-ng-nm3', + 'nanogram per normal cubic meter (ng/Nm³)' + ), + id: 'conngNm3', + fn: toFixedUnit('ng/Nm³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-ug-m3', + 'microgram per cubic meter (μg/m³)' + ), + id: 'conμgm3', + fn: toFixedUnit('μg/m³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-ug-nm3', + 'microgram per normal cubic meter (μg/Nm³)' + ), + id: 'conμgNm3', + fn: toFixedUnit('μg/Nm³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-mg-m3', + 'milligram per cubic meter (mg/m³)' + ), + id: 'conmgm3', + fn: toFixedUnit('mg/m³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-mg-nm3', + 'milligram per normal cubic meter (mg/Nm³)' + ), + id: 'conmgNm3', + fn: toFixedUnit('mg/Nm³'), + }, + { + name: t('grafana-data.valueFormats.categories.concentration.formats.name-g-m3', 'gram per cubic meter (g/m³)'), + id: 'congm3', + fn: toFixedUnit('g/m³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-g-nm3', + 'gram per normal cubic meter (g/Nm³)' + ), + id: 'congNm3', + fn: toFixedUnit('g/Nm³'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-mg-dl', + 'milligrams per decilitre (mg/dL)' + ), + id: 'conmgdL', + fn: toFixedUnit('mg/dL'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.concentration.formats.name-mmol-l', + 'millimoles per litre (mmol/L)' + ), + id: 'conmmolL', + fn: toFixedUnit('mmol/L'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.currency.name', 'Currency'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-usd', 'Dollars ($)'), + id: 'currencyUSD', + fn: currency('$'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-gbp', 'Pounds (£)'), + id: 'currencyGBP', + fn: currency('£'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-eur', 'Euro (€)'), + id: 'currencyEUR', + fn: currency('€'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-jpy', 'Yen (¥)'), + id: 'currencyJPY', + fn: currency('¥'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-rub', 'Rubles (₽)'), + id: 'currencyRUB', + fn: currency('₽'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-uah', 'Hryvnias (₴)'), + id: 'currencyUAH', + fn: currency('₴'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-brl', 'Real (R$)'), + id: 'currencyBRL', + fn: currency('R$'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-dkk', 'Danish Krone (kr)'), + id: 'currencyDKK', + fn: currency('kr', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-isk', 'Icelandic Króna (kr)'), + id: 'currencyISK', + fn: currency('kr', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-nok', 'Norwegian Krone (kr)'), + id: 'currencyNOK', + fn: currency('kr', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-sek', 'Swedish Krona (kr)'), + id: 'currencySEK', + fn: currency('kr', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-czk', 'Czech koruna (czk)'), + id: 'currencyCZK', + fn: currency('czk'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-chf', 'Swiss franc (CHF)'), + id: 'currencyCHF', + fn: currency('CHF'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-pln', 'Polish Złoty (PLN)'), + id: 'currencyPLN', + fn: currency('PLN'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-btc', 'Bitcoin (฿)'), + id: 'currencyBTC', + fn: currency('฿'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-mbtc', 'Milli Bitcoin (฿)'), + id: 'currencymBTC', + fn: currency('mBTC'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-ubtc', 'Micro Bitcoin (฿)'), + id: 'currencyμBTC', + fn: currency('μBTC'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-zar', 'South African Rand (R)'), + id: 'currencyZAR', + fn: currency('R'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-inr', 'Indian Rupee (₹)'), + id: 'currencyINR', + fn: currency('₹'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-krw', 'South Korean Won (₩)'), + id: 'currencyKRW', + fn: currency('₩'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-idr', 'Indonesian Rupiah (Rp)'), + id: 'currencyIDR', + fn: currency('Rp'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-php', 'Philippine Peso (PHP)'), + id: 'currencyPHP', + fn: currency('PHP'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-vnd', 'Vietnamese Dong (VND)'), + id: 'currencyVND', + fn: currency('đ', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-try', 'Turkish Lira (₺)'), + id: 'currencyTRY', + fn: currency('₺', true), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-myr', 'Malaysian Ringgit (RM)'), + id: 'currencyMYR', + fn: currency('RM'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-xpf', 'CFP franc (XPF)'), + id: 'currencyXPF', + fn: currency('XPF'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-bgn', 'Bulgarian Lev (BGN)'), + id: 'currencyBGN', + fn: currency('BGN'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-pyg', 'Guaraní (₲)'), + id: 'currencyPYG', + fn: currency('₲'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-uyu', 'Uruguay Peso (UYU)'), + id: 'currencyUYU', + fn: currency('UYU'), + }, + { + name: t('grafana-data.valueFormats.categories.currency.formats.name-ils', 'Israeli New Shekels (₪)'), + id: 'currencyILS', + fn: currency('₪'), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.data.name', 'Data'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.data.formats.name-bytes-iec', 'bytes(IEC)'), + id: 'bytes', + fn: binaryPrefix('B'), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-bytes-si', 'bytes(SI)'), + id: 'decbytes', + fn: SIPrefix('B'), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-bits-iec', 'bits(IEC)'), + id: 'bits', + fn: binaryPrefix('b'), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-bits-si', 'bits(SI)'), + id: 'decbits', + fn: SIPrefix('b'), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-kibibytes', 'kibibytes'), + id: 'kbytes', + fn: binaryPrefix('B', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-kilobytes', 'kilobytes'), + id: 'deckbytes', + fn: SIPrefix('B', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-mebibytes', 'mebibytes'), + id: 'mbytes', + fn: binaryPrefix('B', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-megabytes', 'megabytes'), + id: 'decmbytes', + fn: SIPrefix('B', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-gibibytes', 'gibibytes'), + id: 'gbytes', + fn: binaryPrefix('B', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-gigabytes', 'gigabytes'), + id: 'decgbytes', + fn: SIPrefix('B', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-tebibytes', 'tebibytes'), + id: 'tbytes', + fn: binaryPrefix('B', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-terabytes', 'terabytes'), + id: 'dectbytes', + fn: SIPrefix('B', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-pebibytes', 'pebibytes'), + id: 'pbytes', + fn: binaryPrefix('B', 5), + }, + { + name: t('grafana-data.valueFormats.categories.data.formats.name-petabytes', 'petabytes'), + id: 'decpbytes', + fn: SIPrefix('B', 5), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.name', 'Data rate'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-packets-sec', 'packets/sec'), + id: 'pps', + fn: SIPrefix('p/s'), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-bytes-sec-iec', 'bytes/sec(IEC)'), + id: 'binBps', + fn: binaryPrefix('B/s'), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-bytes-sec-si', 'bytes/sec(SI)'), + id: 'Bps', + fn: SIPrefix('B/s'), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-bits-sec-iec', 'bits/sec(IEC)'), + id: 'binbps', + fn: binaryPrefix('b/s'), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-bits-sec-si', 'bits/sec(SI)'), + id: 'bps', + fn: SIPrefix('b/s'), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-kibibytes-sec', 'kibibytes/sec'), + id: 'KiBs', + fn: binaryPrefix('B/s', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-kibibits-sec', 'kibibits/sec'), + id: 'Kibits', + fn: binaryPrefix('b/s', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-kilobytes-sec', 'kilobytes/sec'), + id: 'KBs', + fn: SIPrefix('B/s', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-kilobits-sec', 'kilobits/sec'), + id: 'Kbits', + fn: SIPrefix('b/s', 1), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-mebibytes-sec', 'mebibytes/sec'), + id: 'MiBs', + fn: binaryPrefix('B/s', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-mebibits-sec', 'mebibits/sec'), + id: 'Mibits', + fn: binaryPrefix('b/s', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-megabytes-sec', 'megabytes/sec'), + id: 'MBs', + fn: SIPrefix('B/s', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-megabits-sec', 'megabits/sec'), + id: 'Mbits', + fn: SIPrefix('b/s', 2), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-gibibytes-sec', 'gibibytes/sec'), + id: 'GiBs', + fn: binaryPrefix('B/s', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-gibibits-sec', 'gibibits/sec'), + id: 'Gibits', + fn: binaryPrefix('b/s', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-gigabytes-sec', 'gigabytes/sec'), + id: 'GBs', + fn: SIPrefix('B/s', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-gigabits-sec', 'gigabits/sec'), + id: 'Gbits', + fn: SIPrefix('b/s', 3), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-tebibytes-sec', 'tebibytes/sec'), + id: 'TiBs', + fn: binaryPrefix('B/s', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-tebibits-sec', 'tebibits/sec'), + id: 'Tibits', + fn: binaryPrefix('b/s', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-terabytes-sec', 'terabytes/sec'), + id: 'TBs', + fn: SIPrefix('B/s', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-terabits-sec', 'terabits/sec'), + id: 'Tbits', + fn: SIPrefix('b/s', 4), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-pebibytes-sec', 'pebibytes/sec'), + id: 'PiBs', + fn: binaryPrefix('B/s', 5), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-pebibits-sec', 'pebibits/sec'), + id: 'Pibits', + fn: binaryPrefix('b/s', 5), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-petabytes-sec', 'petabytes/sec'), + id: 'PBs', + fn: SIPrefix('B/s', 5), + }, + { + name: t('grafana-data.valueFormats.categories.data-rate.formats.name-petabits-sec', 'petabits/sec'), + id: 'Pbits', + fn: SIPrefix('b/s', 5), + }, + ], + }, + { + name: t('grafana-data.valueFormats.categories.date-time.name', 'Date & time'), + formats: [ + { + name: t('grafana-data.valueFormats.categories.date-time.formats.name-datetime-iso', 'Datetime ISO'), + id: 'dateTimeAsIso', + fn: dateTimeAsIso, + }, + { + name: t( + 'grafana-data.valueFormats.categories.date-time.formats.name-datetime-iso-no-date', + 'Datetime ISO (No date if today)' + ), + id: 'dateTimeAsIsoNoDateIfToday', + fn: dateTimeAsIsoNoDateIfToday, + }, + { + name: t('grafana-data.valueFormats.categories.date-time.formats.name-datetime-us', 'Datetime US'), + id: 'dateTimeAsUS', + fn: dateTimeAsUS, + }, + { + name: t( + 'grafana-data.valueFormats.categories.date-time.formats.name-datetime-us-no-date', + 'Datetime US (No date if today)' + ), + id: 'dateTimeAsUSNoDateIfToday', + fn: dateTimeAsUSNoDateIfToday, + }, + { + name: t('grafana-data.valueFormats.categories.date-time.formats.name-datetime-local', 'Datetime local'), + id: 'dateTimeAsLocal', + fn: getDateTimeAsLocalFormat(), + }, + { + name: t( + 'grafana-data.valueFormats.categories.date-time.formats.name-datetime-local-no-date', + 'Datetime local (No date if today)' + ), id: 'dateTimeAsLocalNoDateIfToday', fn: getDateTimeAsLocalFormatNoDateIfToday(), }, - { name: 'Datetime default', id: 'dateTimeAsSystem', fn: dateTimeSystemFormatter }, - { name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow }, + { + name: t('grafana-data.valueFormats.categories.date-time.formats.name-datetime-default', 'Datetime default'), + id: 'dateTimeAsSystem', + fn: dateTimeSystemFormatter, + }, + { + name: t('grafana-data.valueFormats.categories.date-time.formats.name-from-now', 'From Now'), + id: 'dateTimeFromNow', + fn: dateTimeFromNow, + }, ], }, { - name: 'Energy', + name: t('grafana-data.valueFormats.categories.energy.name', 'Energy'), formats: [ - { name: 'Watt (W)', id: 'watt', fn: SIPrefix('W') }, - { name: 'Kilowatt (kW)', id: 'kwatt', fn: SIPrefix('W', 1) }, - { name: 'Megawatt (MW)', id: 'megwatt', fn: SIPrefix('W', 2) }, - { name: 'Gigawatt (GW)', id: 'gwatt', fn: SIPrefix('W', 3) }, - { name: 'Milliwatt (mW)', id: 'mwatt', fn: SIPrefix('W', -1) }, - { name: 'Watt per square meter (W/m²)', id: 'Wm2', fn: SIPrefix('W/m²') }, - { name: 'Volt-Ampere (VA)', id: 'voltamp', fn: SIPrefix('VA') }, - { name: 'Kilovolt-Ampere (kVA)', id: 'kvoltamp', fn: SIPrefix('VA', 1) }, - { name: 'Volt-Ampere reactive (VAr)', id: 'voltampreact', fn: SIPrefix('VAr') }, - { name: 'Kilovolt-Ampere reactive (kVAr)', id: 'kvoltampreact', fn: SIPrefix('VAr', 1) }, - { name: 'Watt-hour (Wh)', id: 'watth', fn: SIPrefix('Wh') }, - { name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg', fn: SIPrefix('Wh/kg') }, - { name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: SIPrefix('Wh', 1) }, - { name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: SIPrefix('W-Min', 1) }, - { name: 'Megawatt-hour (MWh)', id: 'mwatth', fn: SIPrefix('Wh', 2) }, - { name: 'Ampere-hour (Ah)', id: 'amph', fn: SIPrefix('Ah') }, - { name: 'Kiloampere-hour (kAh)', id: 'kamph', fn: SIPrefix('Ah', 1) }, - { name: 'Milliampere-hour (mAh)', id: 'mamph', fn: SIPrefix('Ah', -1) }, - { name: 'Joule (J)', id: 'joule', fn: SIPrefix('J') }, - { name: 'Electron volt (eV)', id: 'ev', fn: SIPrefix('eV') }, - { name: 'Ampere (A)', id: 'amp', fn: SIPrefix('A') }, - { name: 'Kiloampere (kA)', id: 'kamp', fn: SIPrefix('A', 1) }, - { name: 'Milliampere (mA)', id: 'mamp', fn: SIPrefix('A', -1) }, - { name: 'Volt (V)', id: 'volt', fn: SIPrefix('V') }, - { name: 'Kilovolt (kV)', id: 'kvolt', fn: SIPrefix('V', 1) }, - { name: 'Millivolt (mV)', id: 'mvolt', fn: SIPrefix('V', -1) }, - { name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: SIPrefix('dBm') }, - { name: 'Milliohm (mΩ)', id: 'mohm', fn: SIPrefix('Ω', -1) }, - { name: 'Ohm (Ω)', id: 'ohm', fn: SIPrefix('Ω') }, - { name: 'Kiloohm (kΩ)', id: 'kohm', fn: SIPrefix('Ω', 1) }, - { name: 'Megaohm (MΩ)', id: 'Mohm', fn: SIPrefix('Ω', 2) }, - { name: 'Farad (F)', id: 'farad', fn: SIPrefix('F') }, - { name: 'Microfarad (µF)', id: 'µfarad', fn: SIPrefix('F', -2) }, - { name: 'Nanofarad (nF)', id: 'nfarad', fn: SIPrefix('F', -3) }, - { name: 'Picofarad (pF)', id: 'pfarad', fn: SIPrefix('F', -4) }, - { name: 'Femtofarad (fF)', id: 'ffarad', fn: SIPrefix('F', -5) }, - { name: 'Henry (H)', id: 'henry', fn: SIPrefix('H') }, - { name: 'Millihenry (mH)', id: 'mhenry', fn: SIPrefix('H', -1) }, - { name: 'Microhenry (µH)', id: 'µhenry', fn: SIPrefix('H', -2) }, - { name: 'Lumens (Lm)', id: 'lumens', fn: SIPrefix('Lm') }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-watt', 'Watt (W)'), + id: 'watt', + fn: SIPrefix('W'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kilowatt', 'Kilowatt (kW)'), + id: 'kwatt', + fn: SIPrefix('W', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-megawatt', 'Megawatt (MW)'), + id: 'megwatt', + fn: SIPrefix('W', 2), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-gigawatt', 'Gigawatt (GW)'), + id: 'gwatt', + fn: SIPrefix('W', 3), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-milliwatt', 'Milliwatt (mW)'), + id: 'mwatt', + fn: SIPrefix('W', -1), + }, + { + name: t( + 'grafana-data.valueFormats.categories.energy.formats.name-watt-square-meter', + 'Watt per square meter (W/m²)' + ), + id: 'Wm2', + fn: SIPrefix('W/m²'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-volt-ampere', 'Volt-Ampere (VA)'), + id: 'voltamp', + fn: SIPrefix('VA'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kilovolt-ampere', 'Kilovolt-Ampere (kVA)'), + id: 'kvoltamp', + fn: SIPrefix('VA', 1), + }, + { + name: t( + 'grafana-data.valueFormats.categories.energy.formats.name-volt-ampere-reactive', + 'Volt-Ampere reactive (VAr)' + ), + id: 'voltampreact', + fn: SIPrefix('VAr'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.energy.formats.name-kilovolt-ampere-reactive', + 'Kilovolt-Ampere reactive (kVAr)' + ), + id: 'kvoltampreact', + fn: SIPrefix('VAr', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-watt-hour', 'Watt-hour (Wh)'), + id: 'watth', + fn: SIPrefix('Wh'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.energy.formats.name-watt-hour-per-kg', + 'Watt-hour per Kilogram (Wh/kg)' + ), + id: 'watthperkg', + fn: SIPrefix('Wh/kg'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kilowatt-hour', 'Kilowatt-hour (kWh)'), + id: 'kwatth', + fn: SIPrefix('Wh', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kilowatt-min', 'Kilowatt-min (kWm)'), + id: 'kwattm', + fn: SIPrefix('W-Min', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-megawatt-hour', 'Megawatt-hour (MWh)'), + id: 'mwatth', + fn: SIPrefix('Wh', 2), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-ampere-hour', 'Ampere-hour (Ah)'), + id: 'amph', + fn: SIPrefix('Ah'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kiloampere-hour', 'Kiloampere-hour (kAh)'), + id: 'kamph', + fn: SIPrefix('Ah', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-milliampere-hour', 'Milliampere-hour (mAh)'), + id: 'mamph', + fn: SIPrefix('Ah', -1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-joule', 'Joule (J)'), + id: 'joule', + fn: SIPrefix('J'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-electron-volt', 'Electron volt (eV)'), + id: 'ev', + fn: SIPrefix('eV'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-ampere', 'Ampere (A)'), + id: 'amp', + fn: SIPrefix('A'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kiloampere', 'Kiloampere (kA)'), + id: 'kamp', + fn: SIPrefix('A', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-milliampere', 'Milliampere (mA)'), + id: 'mamp', + fn: SIPrefix('A', -1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-volt', 'Volt (V)'), + id: 'volt', + fn: SIPrefix('V'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kilovolt', 'Kilovolt (kV)'), + id: 'kvolt', + fn: SIPrefix('V', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-millivolt', 'Millivolt (mV)'), + id: 'mvolt', + fn: SIPrefix('V', -1), + }, + { + name: t( + 'grafana-data.valueFormats.categories.energy.formats.name-decibel-milliwatt', + 'Decibel-milliwatt (dBm)' + ), + id: 'dBm', + fn: SIPrefix('dBm'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-milliohm', 'Milliohm (mΩ)'), + id: 'mohm', + fn: SIPrefix('Ω', -1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-ohm', 'Ohm (Ω)'), + id: 'ohm', + fn: SIPrefix('Ω'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-kiloohm', 'Kiloohm (kΩ)'), + id: 'kohm', + fn: SIPrefix('Ω', 1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-megaohm', 'Megaohm (MΩ)'), + id: 'Mohm', + fn: SIPrefix('Ω', 2), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-farad', 'Farad (F)'), + id: 'farad', + fn: SIPrefix('F'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-microfarad', 'Microfarad (µF)'), + id: 'µfarad', + fn: SIPrefix('F', -2), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-nanofarad', 'Nanofarad (nF)'), + id: 'nfarad', + fn: SIPrefix('F', -3), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-picofarad', 'Picofarad (pF)'), + id: 'pfarad', + fn: SIPrefix('F', -4), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-femtofarad', 'Femtofarad (fF)'), + id: 'ffarad', + fn: SIPrefix('F', -5), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-henry', 'Henry (H)'), + id: 'henry', + fn: SIPrefix('H'), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-millihenry', 'Millihenry (mH)'), + id: 'mhenry', + fn: SIPrefix('H', -1), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-microhenry', 'Microhenry (µH)'), + id: 'µhenry', + fn: SIPrefix('H', -2), + }, + { + name: t('grafana-data.valueFormats.categories.energy.formats.name-lumens', 'Lumens (Lm)'), + id: 'lumens', + fn: SIPrefix('Lm'), + }, ], }, { - name: 'Flow', + name: t('grafana-data.valueFormats.categories.flow.name', 'Flow'), formats: [ - { name: 'Gallons/min (gpm)', id: 'flowgpm', fn: toFixedUnit('gpm') }, - { name: 'Cubic meters/sec (cms)', id: 'flowcms', fn: toFixedUnit('cms') }, - { name: 'Cubic feet/sec (cfs)', id: 'flowcfs', fn: toFixedUnit('cfs') }, - { name: 'Cubic feet/min (cfm)', id: 'flowcfm', fn: toFixedUnit('cfm') }, - { name: 'Litre/hour', id: 'litreh', fn: toFixedUnit('L/h') }, - { name: 'Litre/min (L/min)', id: 'flowlpm', fn: toFixedUnit('L/min') }, - { name: 'milliLitre/min (mL/min)', id: 'flowmlpm', fn: toFixedUnit('mL/min') }, - { name: 'Lux (lx)', id: 'lux', fn: toFixedUnit('lux') }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-gallons-min', 'Gallons/min (gpm)'), + id: 'flowgpm', + fn: toFixedUnit('gpm'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-cubic-meters-sec', 'Cubic meters/sec (cms)'), + id: 'flowcms', + fn: toFixedUnit('cms'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-cubic-feet-sec', 'Cubic feet/sec (cfs)'), + id: 'flowcfs', + fn: toFixedUnit('cfs'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-cubic-feet-min', 'Cubic feet/min (cfm)'), + id: 'flowcfm', + fn: toFixedUnit('cfm'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-litre-hour', 'Litre/hour'), + id: 'litreh', + fn: toFixedUnit('L/h'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-litre-min', 'Litre/min (L/min)'), + id: 'flowlpm', + fn: toFixedUnit('L/min'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-millilitre-min', 'milliLitre/min (mL/min)'), + id: 'flowmlpm', + fn: toFixedUnit('mL/min'), + }, + { + name: t('grafana-data.valueFormats.categories.flow.formats.name-lux', 'Lux (lx)'), + id: 'lux', + fn: toFixedUnit('lux'), + }, ], }, { - name: 'Force', + name: t('grafana-data.valueFormats.categories.force.name', 'Force'), formats: [ - { name: 'Newton-meters (Nm)', id: 'forceNm', fn: SIPrefix('Nm') }, - { name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: SIPrefix('Nm', 1) }, - { name: 'Newtons (N)', id: 'forceN', fn: SIPrefix('N') }, - { name: 'Kilonewtons (kN)', id: 'forcekN', fn: SIPrefix('N', 1) }, + { + name: t('grafana-data.valueFormats.categories.force.formats.name-newton-meters', 'Newton-meters (Nm)'), + id: 'forceNm', + fn: SIPrefix('Nm'), + }, + { + name: t('grafana-data.valueFormats.categories.force.formats.name-kilonewton-meters', 'Kilonewton-meters (kNm)'), + id: 'forcekNm', + fn: SIPrefix('Nm', 1), + }, + { + name: t('grafana-data.valueFormats.categories.force.formats.name-newtons', 'Newtons (N)'), + id: 'forceN', + fn: SIPrefix('N'), + }, + { + name: t('grafana-data.valueFormats.categories.force.formats.name-kilonewtons', 'Kilonewtons (kN)'), + id: 'forcekN', + fn: SIPrefix('N', 1), + }, ], }, { - name: 'Hash rate', + name: t('grafana-data.valueFormats.categories.hash-rate.name', 'Hash rate'), formats: [ - { name: 'hashes/sec', id: 'Hs', fn: SIPrefix('H/s') }, - { name: 'kilohashes/sec', id: 'KHs', fn: SIPrefix('H/s', 1) }, - { name: 'megahashes/sec', id: 'MHs', fn: SIPrefix('H/s', 2) }, - { name: 'gigahashes/sec', id: 'GHs', fn: SIPrefix('H/s', 3) }, - { name: 'terahashes/sec', id: 'THs', fn: SIPrefix('H/s', 4) }, - { name: 'petahashes/sec', id: 'PHs', fn: SIPrefix('H/s', 5) }, - { name: 'exahashes/sec', id: 'EHs', fn: SIPrefix('H/s', 6) }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-hashes-sec', 'hashes/sec'), + id: 'Hs', + fn: SIPrefix('H/s'), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-kilohashes-sec', 'kilohashes/sec'), + id: 'KHs', + fn: SIPrefix('H/s', 1), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-megahashes-sec', 'megahashes/sec'), + id: 'MHs', + fn: SIPrefix('H/s', 2), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-gigahashes-sec', 'gigahashes/sec'), + id: 'GHs', + fn: SIPrefix('H/s', 3), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-terahashes-sec', 'terahashes/sec'), + id: 'THs', + fn: SIPrefix('H/s', 4), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-petahashes-sec', 'petahashes/sec'), + id: 'PHs', + fn: SIPrefix('H/s', 5), + }, + { + name: t('grafana-data.valueFormats.categories.hash-rate.formats.name-exahashes-sec', 'exahashes/sec'), + id: 'EHs', + fn: SIPrefix('H/s', 6), + }, ], }, { - name: 'Mass', + name: t('grafana-data.valueFormats.categories.mass.name', 'Mass'), formats: [ - { name: 'milligram (mg)', id: 'massmg', fn: SIPrefix('g', -1) }, - { name: 'gram (g)', id: 'massg', fn: SIPrefix('g') }, - { name: 'pound (lb)', id: 'masslb', fn: toFixedUnit('lb') }, - { name: 'kilogram (kg)', id: 'masskg', fn: SIPrefix('g', 1) }, - { name: 'metric ton (t)', id: 'masst', fn: toFixedUnit('t') }, + { + name: t('grafana-data.valueFormats.categories.mass.formats.name-milligram', 'milligram (mg)'), + id: 'massmg', + fn: SIPrefix('g', -1), + }, + { + name: t('grafana-data.valueFormats.categories.mass.formats.name-gram', 'gram (g)'), + id: 'massg', + fn: SIPrefix('g'), + }, + { + name: t('grafana-data.valueFormats.categories.mass.formats.name-pound', 'pound (lb)'), + id: 'masslb', + fn: toFixedUnit('lb'), + }, + { + name: t('grafana-data.valueFormats.categories.mass.formats.name-kilogram', 'kilogram (kg)'), + id: 'masskg', + fn: SIPrefix('g', 1), + }, + { + name: t('grafana-data.valueFormats.categories.mass.formats.name-metric-ton', 'metric ton (t)'), + id: 'masst', + fn: toFixedUnit('t'), + }, ], }, { - name: 'Length', + name: t('grafana-data.valueFormats.categories.length.name', 'Length'), formats: [ - { name: 'millimeter (mm)', id: 'lengthmm', fn: SIPrefix('m', -1) }, - { name: 'inch (in)', id: 'lengthin', fn: toFixedUnit('in') }, - { name: 'feet (ft)', id: 'lengthft', fn: toFixedUnit('ft') }, - { name: 'meter (m)', id: 'lengthm', fn: SIPrefix('m') }, - { name: 'kilometer (km)', id: 'lengthkm', fn: SIPrefix('m', 1) }, - { name: 'mile (mi)', id: 'lengthmi', fn: toFixedUnit('mi') }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-millimeter', 'millimeter (mm)'), + id: 'lengthmm', + fn: SIPrefix('m', -1), + }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-inch', 'inch (in)'), + id: 'lengthin', + fn: toFixedUnit('in'), + }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-feet', 'feet (ft)'), + id: 'lengthft', + fn: toFixedUnit('ft'), + }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-meter', 'meter (m)'), + id: 'lengthm', + fn: SIPrefix('m'), + }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-kilometer', 'kilometer (km)'), + id: 'lengthkm', + fn: SIPrefix('m', 1), + }, + { + name: t('grafana-data.valueFormats.categories.length.formats.name-mile', 'mile (mi)'), + id: 'lengthmi', + fn: toFixedUnit('mi'), + }, ], }, { - name: 'Pressure', + name: t('grafana-data.valueFormats.categories.pressure.name', 'Pressure'), formats: [ - { name: 'Millibars', id: 'pressurembar', fn: SIPrefix('bar', -1) }, - { name: 'Bars', id: 'pressurebar', fn: SIPrefix('bar') }, - { name: 'Kilobars', id: 'pressurekbar', fn: SIPrefix('bar', 1) }, - { name: 'Pascals', id: 'pressurepa', fn: SIPrefix('Pa') }, - { name: 'Hectopascals', id: 'pressurehpa', fn: toFixedUnit('hPa') }, - { name: 'Kilopascals', id: 'pressurekpa', fn: toFixedUnit('kPa') }, - { name: 'Inches of mercury', id: 'pressurehg', fn: toFixedUnit('"Hg') }, - { name: 'PSI', id: 'pressurepsi', fn: scaledUnits(1000, ['psi', 'ksi', 'Mpsi']) }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-millibars', 'Millibars'), + id: 'pressurembar', + fn: SIPrefix('bar', -1), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-bars', 'Bars'), + id: 'pressurebar', + fn: SIPrefix('bar'), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-kilobars', 'Kilobars'), + id: 'pressurekbar', + fn: SIPrefix('bar', 1), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-pascals', 'Pascals'), + id: 'pressurepa', + fn: SIPrefix('Pa'), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-hectopascals', 'Hectopascals'), + id: 'pressurehpa', + fn: toFixedUnit('hPa'), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-kilopascals', 'Kilopascals'), + id: 'pressurekpa', + fn: toFixedUnit('kPa'), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-inches-mercury', 'Inches of mercury'), + id: 'pressurehg', + fn: toFixedUnit('"Hg'), + }, + { + name: t('grafana-data.valueFormats.categories.pressure.formats.name-psi', 'PSI'), + id: 'pressurepsi', + fn: scaledUnits(1000, ['psi', 'ksi', 'Mpsi']), + }, ], }, { - name: 'Radiation', + name: t('grafana-data.valueFormats.categories.radiation.name', 'Radiation'), formats: [ - { name: 'Becquerel (Bq)', id: 'radbq', fn: SIPrefix('Bq') }, - { name: 'curie (Ci)', id: 'radci', fn: SIPrefix('Ci') }, - { name: 'Gray (Gy)', id: 'radgy', fn: SIPrefix('Gy') }, - { name: 'rad', id: 'radrad', fn: SIPrefix('rad') }, - { name: 'Sievert (Sv)', id: 'radsv', fn: SIPrefix('Sv') }, - { name: 'milliSievert (mSv)', id: 'radmsv', fn: SIPrefix('Sv', -1) }, - { name: 'microSievert (µSv)', id: 'radusv', fn: SIPrefix('Sv', -2) }, - { name: 'rem', id: 'radrem', fn: SIPrefix('rem') }, - { name: 'Exposure (C/kg)', id: 'radexpckg', fn: SIPrefix('C/kg') }, - { name: 'roentgen (R)', id: 'radr', fn: SIPrefix('R') }, - { name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: SIPrefix('Sv/h') }, - { name: 'milliSievert/hour (mSv/h)', id: 'radmsvh', fn: SIPrefix('Sv/h', -1) }, - { name: 'microSievert/hour (µSv/h)', id: 'radusvh', fn: SIPrefix('Sv/h', -2) }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-becquerel', 'Becquerel (Bq)'), + id: 'radbq', + fn: SIPrefix('Bq'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-curie', 'curie (Ci)'), + id: 'radci', + fn: SIPrefix('Ci'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-gray', 'Gray (Gy)'), + id: 'radgy', + fn: SIPrefix('Gy'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-rad', 'rad'), + id: 'radrad', + fn: SIPrefix('rad'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-sievert', 'Sievert (Sv)'), + id: 'radsv', + fn: SIPrefix('Sv'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-millisievert', 'milliSievert (mSv)'), + id: 'radmsv', + fn: SIPrefix('Sv', -1), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-microsievert', 'microSievert (µSv)'), + id: 'radusv', + fn: SIPrefix('Sv', -2), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-rem', 'rem'), + id: 'radrem', + fn: SIPrefix('rem'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-exposure', 'Exposure (C/kg)'), + id: 'radexpckg', + fn: SIPrefix('C/kg'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-roentgen', 'roentgen (R)'), + id: 'radr', + fn: SIPrefix('R'), + }, + { + name: t('grafana-data.valueFormats.categories.radiation.formats.name-sievert-hour', 'Sievert/hour (Sv/h)'), + id: 'radsvh', + fn: SIPrefix('Sv/h'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.radiation.formats.name-millisievert-hour', + 'milliSievert/hour (mSv/h)' + ), + id: 'radmsvh', + fn: SIPrefix('Sv/h', -1), + }, + { + name: t( + 'grafana-data.valueFormats.categories.radiation.formats.name-microsievert-hour', + 'microSievert/hour (µSv/h)' + ), + id: 'radusvh', + fn: SIPrefix('Sv/h', -2), + }, ], }, { - name: 'Rotational Speed', + name: t('grafana-data.valueFormats.categories.rotational-speed.name', 'Rotational Speed'), formats: [ - { name: 'Revolutions per minute (rpm)', id: 'rotrpm', fn: toFixedUnit('rpm') }, - { name: 'Hertz (Hz)', id: 'rothz', fn: SIPrefix('Hz') }, - { name: 'Kilohertz (kHz)', id: 'rotkhz', fn: SIPrefix('Hz', 1) }, - { name: 'Megahertz (MHz)', id: 'rotmhz', fn: SIPrefix('Hz', 2) }, - { name: 'Gigahertz (GHz)', id: 'rotghz', fn: SIPrefix('Hz', 3) }, - { name: 'Radians per second (rad/s)', id: 'rotrads', fn: toFixedUnit('rad/s') }, - { name: 'Degrees per second (°/s)', id: 'rotdegs', fn: toFixedUnit('°/s') }, + { + name: t( + 'grafana-data.valueFormats.categories.rotational-speed.formats.name-rpm', + 'Revolutions per minute (rpm)' + ), + id: 'rotrpm', + fn: toFixedUnit('rpm'), + }, + { + name: t('grafana-data.valueFormats.categories.rotational-speed.formats.name-hertz', 'Hertz (Hz)'), + id: 'rothz', + fn: SIPrefix('Hz'), + }, + { + name: t('grafana-data.valueFormats.categories.rotational-speed.formats.name-kilohertz', 'Kilohertz (kHz)'), + id: 'rotkhz', + fn: SIPrefix('Hz', 1), + }, + { + name: t('grafana-data.valueFormats.categories.rotational-speed.formats.name-megahertz', 'Megahertz (MHz)'), + id: 'rotmhz', + fn: SIPrefix('Hz', 2), + }, + { + name: t('grafana-data.valueFormats.categories.rotational-speed.formats.name-gigahertz', 'Gigahertz (GHz)'), + id: 'rotghz', + fn: SIPrefix('Hz', 3), + }, + { + name: t( + 'grafana-data.valueFormats.categories.rotational-speed.formats.name-radians-sec', + 'Radians per second (rad/s)' + ), + id: 'rotrads', + fn: toFixedUnit('rad/s'), + }, + { + name: t( + 'grafana-data.valueFormats.categories.rotational-speed.formats.name-degrees-sec', + 'Degrees per second (°/s)' + ), + id: 'rotdegs', + fn: toFixedUnit('°/s'), + }, ], }, { - name: 'Temperature', + name: t('grafana-data.valueFormats.categories.temperature.name', 'Temperature'), formats: [ - { name: 'Celsius (°C)', id: 'celsius', fn: toFixedUnit('°C') }, - { name: 'Fahrenheit (°F)', id: 'fahrenheit', fn: toFixedUnit('°F') }, - { name: 'Kelvin (K)', id: 'kelvin', fn: toFixedUnit('K') }, + { + name: t('grafana-data.valueFormats.categories.temperature.formats.name-celsius', 'Celsius (°C)'), + id: 'celsius', + fn: toFixedUnit('°C'), + }, + { + name: t('grafana-data.valueFormats.categories.temperature.formats.name-fahrenheit', 'Fahrenheit (°F)'), + id: 'fahrenheit', + fn: toFixedUnit('°F'), + }, + { + name: t('grafana-data.valueFormats.categories.temperature.formats.name-kelvin', 'Kelvin (K)'), + id: 'kelvin', + fn: toFixedUnit('K'), + }, ], }, { - name: 'Time', + name: t('grafana-data.valueFormats.categories.time.name', 'Time'), formats: [ - { name: 'Hertz (1/s)', id: 'hertz', fn: SIPrefix('Hz') }, - { name: 'nanoseconds (ns)', id: 'ns', fn: toNanoSeconds }, - { name: 'microseconds (µs)', id: 'µs', fn: toMicroSeconds }, - { name: 'milliseconds (ms)', id: 'ms', fn: toMilliSeconds }, - { name: 'seconds (s)', id: 's', fn: toSeconds }, - { name: 'minutes (m)', id: 'm', fn: toMinutes }, - { name: 'hours (h)', id: 'h', fn: toHours }, - { name: 'days (d)', id: 'd', fn: toDays }, - { name: 'duration (ms)', id: 'dtdurationms', fn: toDurationInMilliseconds }, - { name: 'duration (s)', id: 'dtdurations', fn: toDurationInSeconds }, - { name: 'duration (hh:mm:ss)', id: 'dthms', fn: toDurationInHoursMinutesSeconds }, - { name: 'duration (d hh:mm:ss)', id: 'dtdhms', fn: toDurationInDaysHoursMinutesSeconds }, - { name: 'Timeticks (s/100)', id: 'timeticks', fn: toTimeTicks }, - { name: 'clock (ms)', id: 'clockms', fn: toClockMilliseconds }, - { name: 'clock (s)', id: 'clocks', fn: toClockSeconds }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-hertz', 'Hertz (1/s)'), + id: 'hertz', + fn: SIPrefix('Hz'), + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-nanoseconds', 'nanoseconds (ns)'), + id: 'ns', + fn: toNanoSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-microseconds', 'microseconds (µs)'), + id: 'µs', + fn: toMicroSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-milliseconds', 'milliseconds (ms)'), + id: 'ms', + fn: toMilliSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-seconds', 'seconds (s)'), + id: 's', + fn: toSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-minutes', 'minutes (m)'), + id: 'm', + fn: toMinutes, + }, + { name: t('grafana-data.valueFormats.categories.time.formats.name-hours', 'hours (h)'), id: 'h', fn: toHours }, + { name: t('grafana-data.valueFormats.categories.time.formats.name-days', 'days (d)'), id: 'd', fn: toDays }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-duration-ms', 'duration (ms)'), + id: 'dtdurationms', + fn: toDurationInMilliseconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-duration-s', 'duration (s)'), + id: 'dtdurations', + fn: toDurationInSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-duration-hms', 'duration (hh:mm:ss)'), + id: 'dthms', + fn: toDurationInHoursMinutesSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-duration-dhms', 'duration (d hh:mm:ss)'), + id: 'dtdhms', + fn: toDurationInDaysHoursMinutesSeconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-timeticks', 'Timeticks (s/100)'), + id: 'timeticks', + fn: toTimeTicks, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-clock-ms', 'clock (ms)'), + id: 'clockms', + fn: toClockMilliseconds, + }, + { + name: t('grafana-data.valueFormats.categories.time.formats.name-clock-s', 'clock (s)'), + id: 'clocks', + fn: toClockSeconds, + }, ], }, { - name: 'Throughput', + name: t('grafana-data.valueFormats.categories.throughput.name', 'Throughput'), formats: [ - { name: 'counts/sec (cps)', id: 'cps', fn: simpleCountUnit('c/s') }, - { name: 'ops/sec (ops)', id: 'ops', fn: simpleCountUnit('ops/s') }, - { name: 'requests/sec (rps)', id: 'reqps', fn: simpleCountUnit('req/s') }, - { name: 'reads/sec (rps)', id: 'rps', fn: simpleCountUnit('rd/s') }, - { name: 'writes/sec (wps)', id: 'wps', fn: simpleCountUnit('wr/s') }, - { name: 'I/O ops/sec (iops)', id: 'iops', fn: simpleCountUnit('io/s') }, - { name: 'events/sec (eps)', id: 'eps', fn: simpleCountUnit('evt/s') }, - { name: 'messages/sec (mps)', id: 'mps', fn: simpleCountUnit('msg/s') }, - { name: 'records/sec (rps)', id: 'recps', fn: simpleCountUnit('rec/s') }, - { name: 'rows/sec (rps)', id: 'rowsps', fn: simpleCountUnit('rows/s') }, - { name: 'counts/min (cpm)', id: 'cpm', fn: simpleCountUnit('c/m') }, - { name: 'ops/min (opm)', id: 'opm', fn: simpleCountUnit('ops/m') }, - { name: 'requests/min (rpm)', id: 'reqpm', fn: simpleCountUnit('req/m') }, - { name: 'reads/min (rpm)', id: 'rpm', fn: simpleCountUnit('rd/m') }, - { name: 'writes/min (wpm)', id: 'wpm', fn: simpleCountUnit('wr/m') }, - { name: 'events/min (epm)', id: 'epm', fn: simpleCountUnit('evts/m') }, - { name: 'messages/min (mpm)', id: 'mpm', fn: simpleCountUnit('msgs/m') }, - { name: 'records/min (rpm)', id: 'recpm', fn: simpleCountUnit('rec/m') }, - { name: 'rows/min (rpm)', id: 'rowspm', fn: simpleCountUnit('rows/m') }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-counts-sec', 'counts/sec (cps)'), + id: 'cps', + fn: simpleCountUnit('c/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-ops-sec', 'ops/sec (ops)'), + id: 'ops', + fn: simpleCountUnit('ops/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-requests-sec', 'requests/sec (rps)'), + id: 'reqps', + fn: simpleCountUnit('req/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-reads-sec', 'reads/sec (rps)'), + id: 'rps', + fn: simpleCountUnit('rd/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-writes-sec', 'writes/sec (wps)'), + id: 'wps', + fn: simpleCountUnit('wr/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-io-ops-sec', 'I/O ops/sec (iops)'), + id: 'iops', + fn: simpleCountUnit('io/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-events-sec', 'events/sec (eps)'), + id: 'eps', + fn: simpleCountUnit('evt/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-messages-sec', 'messages/sec (mps)'), + id: 'mps', + fn: simpleCountUnit('msg/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-records-sec', 'records/sec (rps)'), + id: 'recps', + fn: simpleCountUnit('rec/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-rows-sec', 'rows/sec (rps)'), + id: 'rowsps', + fn: simpleCountUnit('rows/s'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-counts-min', 'counts/min (cpm)'), + id: 'cpm', + fn: simpleCountUnit('c/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-ops-min', 'ops/min (opm)'), + id: 'opm', + fn: simpleCountUnit('ops/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-requests-min', 'requests/min (rpm)'), + id: 'reqpm', + fn: simpleCountUnit('req/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-reads-min', 'reads/min (rpm)'), + id: 'rpm', + fn: simpleCountUnit('rd/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-writes-min', 'writes/min (wpm)'), + id: 'wpm', + fn: simpleCountUnit('wr/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-events-min', 'events/min (epm)'), + id: 'epm', + fn: simpleCountUnit('evts/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-messages-min', 'messages/min (mpm)'), + id: 'mpm', + fn: simpleCountUnit('msgs/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-records-min', 'records/min (rpm)'), + id: 'recpm', + fn: simpleCountUnit('rec/m'), + }, + { + name: t('grafana-data.valueFormats.categories.throughput.formats.name-rows-min', 'rows/min (rpm)'), + id: 'rowspm', + fn: simpleCountUnit('rows/m'), + }, ], }, { - name: 'Velocity', + name: t('grafana-data.valueFormats.categories.velocity.name', 'Velocity'), formats: [ - { name: 'meters/second (m/s)', id: 'velocityms', fn: toFixedUnit('m/s') }, - { name: 'kilometers/hour (km/h)', id: 'velocitykmh', fn: toFixedUnit('km/h') }, - { name: 'miles/hour (mph)', id: 'velocitymph', fn: toFixedUnit('mph') }, - { name: 'knot (kn)', id: 'velocityknot', fn: toFixedUnit('kn') }, + { + name: t('grafana-data.valueFormats.categories.velocity.formats.name-meters-second', 'meters/second (m/s)'), + id: 'velocityms', + fn: toFixedUnit('m/s'), + }, + { + name: t('grafana-data.valueFormats.categories.velocity.formats.name-kilometers-hour', 'kilometers/hour (km/h)'), + id: 'velocitykmh', + fn: toFixedUnit('km/h'), + }, + { + name: t('grafana-data.valueFormats.categories.velocity.formats.name-miles-hour', 'miles/hour (mph)'), + id: 'velocitymph', + fn: toFixedUnit('mph'), + }, + { + name: t('grafana-data.valueFormats.categories.velocity.formats.name-knot', 'knot (kn)'), + id: 'velocityknot', + fn: toFixedUnit('kn'), + }, ], }, { - name: 'Volume', + name: t('grafana-data.valueFormats.categories.volume.name', 'Volume'), formats: [ - { name: 'millilitre (mL)', id: 'mlitre', fn: SIPrefix('L', -1) }, - { name: 'litre (L)', id: 'litre', fn: SIPrefix('L') }, - { name: 'cubic meter', id: 'm3', fn: toFixedUnit('m³') }, - { name: 'Normal cubic meter', id: 'Nm3', fn: toFixedUnit('Nm³') }, - { name: 'cubic decimeter', id: 'dm3', fn: toFixedUnit('dm³') }, - { name: 'gallons', id: 'gallons', fn: toFixedUnit('gal') }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-millilitre', 'millilitre (mL)'), + id: 'mlitre', + fn: SIPrefix('L', -1), + }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-litre', 'litre (L)'), + id: 'litre', + fn: SIPrefix('L'), + }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-cubic-meter', 'cubic meter'), + id: 'm3', + fn: toFixedUnit('m³'), + }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-normal-cubic-meter', 'Normal cubic meter'), + id: 'Nm3', + fn: toFixedUnit('Nm³'), + }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-cubic-decimeter', 'cubic decimeter'), + id: 'dm3', + fn: toFixedUnit('dm³'), + }, + { + name: t('grafana-data.valueFormats.categories.volume.formats.name-gallons', 'gallons'), + id: 'gallons', + fn: toFixedUnit('gal'), + }, ], }, { - name: 'Boolean', + name: t('grafana-data.valueFormats.categories.boolean.name', 'Boolean'), formats: [ - { name: 'True / False', id: 'bool', fn: booleanValueFormatter('True', 'False') }, - { name: 'Yes / No', id: 'bool_yes_no', fn: booleanValueFormatter('Yes', 'No') }, - { name: 'On / Off', id: 'bool_on_off', fn: booleanValueFormatter('On', 'Off') }, + { + name: t('grafana-data.valueFormats.categories.boolean.formats.name-true-false', 'True / False'), + id: 'bool', + fn: booleanValueFormatter('True', 'False'), + }, + { + name: t('grafana-data.valueFormats.categories.boolean.formats.name-yes-no', 'Yes / No'), + id: 'bool_yes_no', + fn: booleanValueFormatter('Yes', 'No'), + }, + { + name: t('grafana-data.valueFormats.categories.boolean.formats.name-on-off', 'On / Off'), + id: 'bool_on_off', + fn: booleanValueFormatter('On', 'Off'), + }, ], }, ]; diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 265adc2ffdb..d647d0fb88d 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", @@ -40,12 +40,12 @@ }, "devDependencies": { "@rollup/plugin-node-resolve": "16.0.1", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/semver": "7.7.0", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0" }, "dependencies": { diff --git a/packages/grafana-e2e-selectors/rollup.config.ts b/packages/grafana-e2e-selectors/rollup.config.ts index 9888b316a6a..1d47540a42a 100644 --- a/packages/grafana-e2e-selectors/rollup.config.ts +++ b/packages/grafana-e2e-selectors/rollup.config.ts @@ -10,5 +10,6 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-e2e-selectors')], + treeshake: false, }, ]; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 93e5a545d8c..adf4f2efbfe 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -481,6 +481,19 @@ export const versionedComponents = { '10.2.0': 'data-testid table body', }, }, + TableNG: { + Filters: { + HeaderButton: { + '12.1.0': 'data-testid tableng header filter', + }, + Container: { + '12.1.0': 'data-testid tablenf filter container', + }, + SelectAll: { + '12.1.0': 'data-testid tableng filter select-all', + }, + }, + }, }, }, VizLegend: { diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index 58f95284f26..71203b6d524 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,7 +1,7 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", @@ -18,7 +18,7 @@ }, "devDependencies": { "@typescript-eslint/types": "^8.9.0", - "eslint": "9.19.0", + "eslint": "9.32.0", "tslib": "2.8.1" }, "private": true diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 892771c1082..f8597f17c32 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "@leeoniya/ufuzzy": "1.0.18", "d3": "^7.8.5", "lodash": "4.17.21", @@ -56,28 +56,28 @@ "tslib": "2.8.1" }, "devDependencies": { - "@babel/core": "7.26.10", - "@babel/preset-env": "7.26.9", - "@babel/preset-react": "7.26.3", + "@babel/core": "7.28.0", + "@babel/preset-env": "7.28.0", + "@babel/preset-react": "7.27.1", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "16.0.1", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-virtualized-auto-sizer": "1.0.8", "@types/tinycolor2": "1.4.6", "babel-jest": "29.7.0", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "jest": "^29.6.4", "jest-canvas-mock": "2.5.2", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "ts-jest": "29.2.5", "ts-node": "10.9.2", diff --git a/packages/grafana-flamegraph/rollup.config.ts b/packages/grafana-flamegraph/rollup.config.ts index 3bc2d6a540f..967916710d6 100644 --- a/packages/grafana-flamegraph/rollup.config.ts +++ b/packages/grafana-flamegraph/rollup.config.ts @@ -10,5 +10,6 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-flamegraph')], + treeshake: false, }, ]; diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index 9f2fd298de1..6ffd95918f4 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/i18n", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Internationalization Library", "keywords": [ "grafana", diff --git a/packages/grafana-i18n/rollup.config.ts b/packages/grafana-i18n/rollup.config.ts index 3171639a367..1b1115f65c9 100644 --- a/packages/grafana-i18n/rollup.config.ts +++ b/packages/grafana-i18n/rollup.config.ts @@ -16,5 +16,6 @@ export default [ }), ], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-i18n')], + treeshake: false, }, ]; diff --git a/packages/grafana-i18n/src/dates.ts b/packages/grafana-i18n/src/dates.ts index db699e871ae..c75cd2c86f4 100644 --- a/packages/grafana-i18n/src/dates.ts +++ b/packages/grafana-i18n/src/dates.ts @@ -1,8 +1,13 @@ import deepEqual from 'fast-deep-equal'; -import memoize from 'micro-memoize'; +import memoize, { AnyFn, Memoized } from 'micro-memoize'; const deepMemoize: typeof memoize = (fn) => memoize(fn, { isEqual: deepEqual }); +function clearMemoizedCache(fn: Memoized) { + fn.cache.keys.length = 0; + fn.cache.values.length = 0; +} + let regionalFormat: string | undefined; const createDateTimeFormatter = deepMemoize((locale: string | undefined, options: Intl.DateTimeFormatOptions) => { @@ -41,5 +46,10 @@ export const formatDateRange = ( }; export const initRegionalFormat = (regionalFormatArg: string) => { + // We don't expect this to be called with a different locale during the lifetime of the app, + // so this is mostly here so we can change it during tests and clear out previously memoized values. + clearMemoizedCache(formatDate); + clearMemoizedCache(formatDuration); + regionalFormat = regionalFormatArg; }; diff --git a/packages/grafana-i18n/src/index.ts b/packages/grafana-i18n/src/index.ts index a4ebd2b5289..8a83bb17c1b 100644 --- a/packages/grafana-i18n/src/index.ts +++ b/packages/grafana-i18n/src/index.ts @@ -24,4 +24,4 @@ export { } from './constants'; export { initPluginTranslations, t, Trans } from './i18n'; export type { ResourceLoader, Resources, TFunction, TransProps } from './types'; -export { formatDate, formatDuration, formatDateRange } from './dates'; +export { formatDate, formatDuration, formatDateRange, initRegionalFormat as initRegionalFormatForTests } from './dates'; diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 18faf879904..1d56eb47a0e 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "react-select": "5.10.2", "react-use": "17.6.0", "rxjs": "7.8.2", @@ -33,10 +33,10 @@ "@grafana/tsconfig": "^2.0.0", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/systemjs": "6.15.3", "jest": "^29.6.4", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index f9c6f066c3f..3ca7029e86d 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "tslib": "2.8.1" }, @@ -15,9 +15,9 @@ "@types/eslint": "9.6.1", "@types/webpack-bundle-analyzer": "^4.7.0", "copy-webpack-plugin": "12.0.2", - "eslint": "9.19.0", + "eslint": "9.32.0", "eslint-webpack-plugin": "4.2.0", - "fork-ts-checker-webpack-plugin": "9.0.2", + "fork-ts-checker-webpack-plugin": "9.1.0", "glob": "11.0.3", "identity-obj-proxy": "^3.0.0", "imports-loader": "^5.0.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 2927ed8e76c..efd2b239574 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript", @@ -40,14 +40,14 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@floating-ui/react": "0.27.13", - "@grafana/data": "12.1.0-pre", - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/i18n": "12.1.0-pre", + "@floating-ui/react": "0.27.14", + "@grafana/data": "12.2.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/i18n": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "@hello-pangea/dnd": "18.0.1", "@leeoniya/ufuzzy": "1.0.18", "@lezer/common": "1.2.3", @@ -67,7 +67,7 @@ "lodash": "4.17.21", "moment": "2.30.1", "moment-timezone": "0.5.47", - "monaco-promql": "1.7.4", + "monaco-promql": "1.8.0", "pluralize": "8.0.0", "prismjs": "1.30.0", "react-highlight-words": "0.21.0", @@ -84,13 +84,13 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.1", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "i18next-parser": "9.3.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", @@ -99,7 +99,7 @@ "react-select-event": "5.5.1", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "testing-library-selector": "0.3.1", "typescript": "5.8.3" diff --git a/packages/grafana-prometheus/rollup.config.ts b/packages/grafana-prometheus/rollup.config.ts index be421e6c7aa..8dbc60c8935 100644 --- a/packages/grafana-prometheus/rollup.config.ts +++ b/packages/grafana-prometheus/rollup.config.ts @@ -13,5 +13,6 @@ export default [ input: entryPoint, plugins: [...plugins, image(), json(), dynamicImportVars()], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-prometheus')], + treeshake: false, }, ]; diff --git a/packages/grafana-prometheus/src/add_label_to_query.ts b/packages/grafana-prometheus/src/add_label_to_query.ts index 7c75e6e14ec..06e82fcbf00 100644 --- a/packages/grafana-prometheus/src/add_label_to_query.ts +++ b/packages/grafana-prometheus/src/add_label_to_query.ts @@ -21,7 +21,7 @@ import { PromVisualQuery } from './querybuilder/types'; * @param operator */ export function addLabelToQuery(query: string, key: string, value: string | number, operator = '='): string { - if (!key || !value) { + if (!key) { throw new Error('Need label to add to query.'); } diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts index 88303e2503b..e6df676beba 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts @@ -1,7 +1,7 @@ import { config } from '@grafana/runtime'; import { SUGGESTIONS_LIMIT } from '../../../constants'; -import { FUNCTIONS } from '../../../promql'; +import { getFunctions } from '../../../promql'; import { getMockTimeRange } from '../../../test/mocks/datasource'; import { filterMetricNames, getCompletions } from './completions'; @@ -172,7 +172,7 @@ const metricNameCompletionSituations = ['AT_ROOT', 'IN_FUNCTION', 'EMPTY'] as Me function getSuggestionCountForSituation(situationType: MetricNameSituation, metricsCount: number): number { const limitedMetricNamesCount = metricsCount < SUGGESTIONS_LIMIT ? metricsCount : SUGGESTIONS_LIMIT; - let suggestionsCount = limitedMetricNamesCount + FUNCTIONS.length; + let suggestionsCount = limitedMetricNamesCount + getFunctions().length; if (situationType === 'EMPTY') { suggestionsCount += history.length; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts index def4cd7eaaa..f7fc08f8bce 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.ts @@ -6,7 +6,7 @@ import { TimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; import { escapeLabelValueInExactSelector, prometheusRegularEscape } from '../../../escaping'; -import { FUNCTIONS } from '../../../promql'; +import { getFunctions } from '../../../promql'; import { isValidLegacyName } from '../../../utf8_support'; import { DataProvider } from './data_provider'; @@ -100,18 +100,20 @@ function getAllMetricNamesCompletions(dataProvider: DataProvider): Completion[] })); } -const FUNCTION_COMPLETIONS: Completion[] = FUNCTIONS.map((f) => ({ - type: 'FUNCTION', - label: f.label, - insertText: f.insertText ?? '', // i don't know what to do when this is nullish. it should not be. - detail: f.detail, - documentation: f.documentation, -})); +const getFunctionCompletions: () => Completion[] = () => { + return getFunctions().map((f) => ({ + type: 'FUNCTION', + label: f.label, + insertText: f.insertText ?? '', // i don't know what to do when this is nullish. it should not be. + detail: f.detail, + documentation: f.documentation, + })); +}; async function getAllFunctionsAndMetricNamesCompletions(dataProvider: DataProvider): Promise { const metricNames = getAllMetricNamesCompletions(dataProvider); - return [...FUNCTION_COMPLETIONS, ...metricNames]; + return [...getFunctionCompletions(), ...metricNames]; } const DURATION_COMPLETIONS: Completion[] = [ @@ -276,7 +278,7 @@ export function getCompletions( case 'EMPTY': { const metricNames = getAllMetricNamesCompletions(dataProvider); const historyCompletions = getAllHistoryCompletions(dataProvider); - return Promise.resolve([...historyCompletions, ...FUNCTION_COMPLETIONS, ...metricNames]); + return Promise.resolve([...historyCompletions, ...getFunctionCompletions(), ...metricNames]); } case 'IN_LABEL_SELECTOR_NO_LABEL_NAME': return getLabelNamesForSelectorCompletions(situation.metricName, situation.otherLabels, dataProvider, timeRange); diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index 3ff374771d7..0419295ab16 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -515,19 +515,6 @@ export default class PromQlLanguageProvider extends LanguageProvider implements }; } -export interface PrometheusLanguageProviderInterface - extends PrometheusBaseLanguageProvider, - PrometheusLegacyLanguageProvider { - retrieveMetricsMetadata: () => PromMetricsMetadata; - retrieveHistogramMetrics: () => string[]; - retrieveMetrics: () => string[]; - retrieveLabelKeys: () => string[]; - - queryMetricsMetadata: (limit?: number) => Promise; - queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: number) => Promise; - queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: number) => Promise; -} - /** * Modern implementation of the Prometheus language provider that abstracts API endpoint selection. * @@ -535,12 +522,73 @@ export interface PrometheusLanguageProviderInterface * - Automatically selects the most efficient API endpoint based on Prometheus version and configuration * - Supports both labels and series endpoints for backward compatibility * - Handles match[] parameters for filtering time series data - * - Implements automatic request limiting (default: 40_000 series) + * - Implements automatic request limiting (default: 40,000 series if not configured otherwise) * - Provides unified interface for both modern and legacy Prometheus versions + * - Provides caching mechanism based on time range, limit, and match parameters * * @see LabelsApiClient For modern Prometheus versions using the labels API * @see SeriesApiClient For legacy Prometheus versions using the series API */ +export interface PrometheusLanguageProviderInterface + extends PrometheusBaseLanguageProvider, + PrometheusLegacyLanguageProvider { + /** + * Initializes the language provider by fetching metrics, label keys, and metrics metadata using Resource Clients. + * All calls use the limit parameter from datasource configuration (default: 40,000 if not set). + * + * For backward compatibility, it calls _backwardCompatibleStart. + * Some places still rely on deprecated fields. Until we replace them, we need _backwardCompatibleStart method. + */ + start: (timeRange?: TimeRange) => Promise; + + /** + * Returns already cached metrics metadata including type and help information. + * If there is no cached metadata, it returns an empty object. + * To get fresh metadata, use queryMetricsMetadata instead. + */ + retrieveMetricsMetadata: () => PromMetricsMetadata; + + /** + * Returns already cached list of histogram metrics (identified by '_bucket' suffix). + * If there are no cached histogram metrics, it returns an empty array. + */ + retrieveHistogramMetrics: () => string[]; + + /** + * Returns already cached list of all available metric names. + * If there are no cached metrics, it returns an empty array. + */ + retrieveMetrics: () => string[]; + + /** + * Returns already cached list of available label keys. + * If there are no cached label keys, it returns an empty array. + */ + retrieveLabelKeys: () => string[]; + + /** + * Fetches fresh metrics metadata from Prometheus with optional limit. + * Uses datasource's default limit if not specified. + */ + queryMetricsMetadata: (limit?: number) => Promise; + + /** + * Queries Prometheus for label keys within time range, optionally filtered by match selector. + * Automatically selects labels or series endpoint based on datasource configuration. + * If no limit is provided, uses the datasource's default limit configuration. + * Use zero (0) to fetch all label keys, but this might return huge amounts of data. + */ + queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: number) => Promise; + + /** + * Queries Prometheus for values of a specific label key, optionally filtered by match selector. + * Automatically selects labels or series endpoint based on datasource configuration. + * If no limit is provided, uses the datasource's default limit configuration. + * Use zero (0) to fetch all label values, but this might return huge amounts of data. + */ + queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: number) => Promise; +} + export class PrometheusLanguageProvider extends PromQlLanguageProvider implements PrometheusLanguageProviderInterface { private _metricsMetadata?: PromMetricsMetadata; private _resourceClient?: ResourceApiClient; diff --git a/packages/grafana-prometheus/src/language_utils.test.ts b/packages/grafana-prometheus/src/language_utils.test.ts index 5ba14363da7..555634f898e 100644 --- a/packages/grafana-prometheus/src/language_utils.test.ts +++ b/packages/grafana-prometheus/src/language_utils.test.ts @@ -191,6 +191,18 @@ describe('expandRecordingRules()', () => { const result = expandRecordingRules(query, mapping); expect(result).toBe(expected); }); + + it('when there is an empty label value it should still be able to expand the rule', () => { + const query = `sum(max by (cluster, container) (pod_cpu:active:kube_limits{container!="", cluster=~"pink"}))`; + const mapping = { + 'pod_cpu:active:kube_limits': { + expandedQuery: `kube_limits{job!="", resource="cpu"} * on (namespace, pod, cluster) group_left () max by (namespace, pod, cluster) ((kube_pod_status_phase{phase=~"Pending|Running"} == 1))`, + }, + }; + const expected = `sum(max by (cluster, container) (kube_limits{job!="", resource="cpu", container!="", cluster=~"pink"} * on (namespace, pod, cluster) group_left () max by (namespace, pod, cluster) ((kube_pod_status_phase{phase=~"Pending|Running", container!="", cluster=~"pink"} == 1))))`; + const result = expandRecordingRules(query, mapping); + expect(result).toBe(expected); + }); }); describe('escapeLabelValueInExactSelector()', () => { diff --git a/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json index 8498c0ce6df..57f9cec30c7 100644 --- a/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "Filtrovat názvy metrik podle vyhledávání regulárních výrazů pomocí dalšího volání rozhraní Prometheus API.", @@ -321,6 +418,13 @@ "title-add": "Přidat {{name}}", "title-remove": "Odebrat {{name}}" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "Nastavit dolní limit pro parametr kroku", "aria-label-select-resolution": "Vyberte rozlišení", diff --git a/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json index b34304ade03..d3c2c2e1743 100644 --- a/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json @@ -2,201 +2,201 @@ "grafana-prometheus": { "components": { "annotation-query-editor": { - "annotation-data-load-error": "", - "aria-label-lower-limit-parameter": "", - "label-min-step": "", - "label-series-value-as-timestamp": "", + "annotation-data-load-error": "Fehler beim Laden der Anmerkungsdaten!", + "aria-label-lower-limit-parameter": "Untergrenze für den Schrittparameter festlegen", + "label-min-step": "Min. Schritt", + "label-series-value-as-timestamp": "Reihenwert als Zeitstempel", "label-tags": "Tags", - "label-text": "", + "label-text": "Text", "label-title": "Titel", - "placeholder-auto": "", - "tooltip-either-pattern-example-instance-replaced-label": "", - "tooltip-min-step": "", - "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "" + "placeholder-auto": "Auto", + "tooltip-either-pattern-example-instance-replaced-label": "Verwenden Sie entweder den Namen oder ein Muster. Ein Beispiel: {{labelTemplate}} wird durch den Label-Wert für das Label {{labelName}} ersetzt.", + "tooltip-min-step": "Eine zusätzliche Untergrenze für den Schrittparameter der Prometheus-Abfrage und für die Variablen <2>{{intervalVar}} und <4>{{rateIntervalVar}}.", + "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "Die Einheit des Zeitstempels ist Millisekunden. Wenn die Einheit des Reihenwerts Sekunden ist, multiplizieren Sie den Bereichsvektor mit 1000." }, "get-query-type-options": { "description": { - "instant-query-range": "" + "instant-query-range": "Eine Instant-Abfrage und eine Bereichsabfrage ausführen" }, "label": { - "both": "" + "both": "Beides" }, "range-options": { "description": { - "query-range": "" + "query-range": "Abfrage über einen bestimmten Zeitraum ausführen" }, "label": { - "instant": "", - "range": "" + "instant": "Instant", + "range": "Bereich" } } }, "label-selector": { - "aria-label-filter-expression-for-label": "", - "description-select-labels": "", - "select-labels-to-search-in": "" + "aria-label-filter-expression-for-label": "Filterausdruck für Label", + "description-select-labels": "Sobald die Label-Werte ausgewählt sind, werden nur mögliche Label-Kombinationen angezeigt.", + "select-labels-to-search-in": "2. Wählen Sie Labels aus, in denen gesucht werden soll" }, "metric-selector": { - "aria-label-filter-expression-for-metric": "", - "aria-label-limit-results-from-series-endpoint": "", - "description-series-limit": "", - "label-select-metric": "", - "select-a-metric": "", - "series-limit": "" + "aria-label-filter-expression-for-metric": "Filterausdruck für Metrik", + "aria-label-limit-results-from-series-endpoint": "Ergebnisse vom Reihenendpunkt begrenzen", + "description-series-limit": "Die Grenze gilt für alle Metriken, Labels und Werte. Wenn Sie das Feld leer lassen, wird die Standardgrenze verwendet. Durch eine Einstellung auf 0 wird die Grenze deaktiviert und alles abgerufen – dies kann zu Leistungsproblemen führen.", + "label-select-metric": "Sobald eine Metrik ausgewählt ist, werden nur mögliche Labels angezeigt. Labels werden durch die folgende Reihengrenze begrenzt.", + "select-a-metric": "1. Metrik auswählen", + "series-limit": "Reihengrenze" }, "prom-cheat-sheet": { - "prom-ql-cheat-sheet": "" + "prom-ql-cheat-sheet": "PromQL Cheat Sheet" }, "prom-exemplar-field": { - "exemplars": "", - "tooltip-disable-query": "", - "tooltip-enable-query": "" + "exemplars": "Exemplare", + "tooltip-disable-query": "Abfrage mit Exemplaren deaktivieren", + "tooltip-enable-query": "Abfrage mit Exemplaren aktivieren" }, "prom-explore-extra-field": { - "aria-label-prometheus-extra-field": "", - "aria-label-query-type-field": "", - "aria-label-step-field": "", - "min-step": "", - "query-type": "", - "tooltip-units-builtin-variables-example-interval-rateinterval": "" + "aria-label-prometheus-extra-field": "Zusätzliches Feld in Prometheus", + "aria-label-query-type-field": "Abfragetyp-Feld", + "aria-label-step-field": "Schrittfeld", + "min-step": "Min. Schritt", + "query-type": "Abfragetyp", + "tooltip-units-builtin-variables-example-interval-rateinterval": "Hier können Zeiteinheiten und integrierte Variablen verwendet werden, zum Beispiel: {{example1}}, {{example2}}, {{example3}}, {{example4}}, {{example5}}, {{example6}}, {{example7}} (Standardwert bei fehlender Einheit: {{default}})" }, "prom-query-field": { - "placeholder-enter-a-prom-ql-query": "" + "placeholder-enter-a-prom-ql-query": "Geben Sie eine PromQL-Abfrage ein …" }, "prom-variable-query-editor": { - "aria-label-classic-query": "", - "aria-label-metric-regex": "", - "aria-label-metric-selector": "", - "aria-label-prometheus-query": "", - "aria-label-query-type": "", - "aria-label-series-query": "", - "label-classic-query": "", + "aria-label-classic-query": "Klassische Abfrage", + "aria-label-metric-regex": "Metrik-Regex", + "aria-label-metric-selector": "Metrik-Selektor", + "aria-label-prometheus-query": "Prometheus-Abfrage", + "aria-label-query-type": "Abfragetyp", + "aria-label-series-query": "Reihenabfrage", + "label-classic-query": "Klassische Abfrage", "label-label": "Label", - "label-metric-regex": "", + "label-metric-regex": "Metrik-RegEx", "label-query": "Abfrage", - "label-query-type": "", - "label-series-query": "", - "placeholder-classic-query": "", - "placeholder-metric-regex": "", - "placeholder-prometheus-query": "", + "label-query-type": "Abfragetyp", + "label-series-query": "Reihenabfrage", + "placeholder-classic-query": "Klassische Abfrage", + "placeholder-metric-regex": "Metrik-Regex", + "placeholder-prometheus-query": "Prometheus-Abfrage", "placeholder-select-query-type": "Abfragetyp auswählen", - "placeholder-series-query": "", - "returns-metrics-matching-specified-metric-regex": "", - "tooltip-classic-query": "", - "tooltip-label": "", - "tooltip-metric-regex": "", - "tooltip-query": "", - "tooltip-query-type": "", - "tooltip-series-query": "" + "placeholder-series-query": "Reihenabfrage", + "returns-metrics-matching-specified-metric-regex": "Gibt eine Liste von Metriken zurück, die mit dem angegebenen Metrik-Regex übereinstimmen.", + "tooltip-classic-query": "Die ursprüngliche Implementierung des Prometheus-Variablen-Abfrageeditors. Geben Sie eine Zeichenfolge mit dem richtigen Abfragetyp und den richtigen Parametern ein, wie in diesen Dokumenten beschrieben. Zum Beispiel: {{exampleQuery}}.", + "tooltip-label": "Gibt eine Liste von Label-Werten für den Label-Namen in allen Metriken zurück, es sei denn, die Metrik ist angegeben.", + "tooltip-metric-regex": "Gibt eine Liste von Label-Namen zurück, die optional gemäß dem angegebenen Metrik-Regex gefiltert werden.", + "tooltip-query": "Gibt eine Liste der Prometheus-Abfrageergebnisse für die Abfrage zurück. Dies kann Prometheus-Funktionen umfassen, z. B. {{exampleQuery}}.", + "tooltip-query-type": "Das Prometheus-Datenquellen-Plugin bietet die folgenden Abfragetypen für Vorlagenvariablen.", + "tooltip-series-query": "Geben Sie eine Metrik mit Labels ein, nur eine Metrik oder nur Labels, z. B. {{example1}}, {{example2}} oder {{example3}}. Gibt eine Liste der Zeitreihen zurück, die den eingegebenen Daten zugeordnet sind." }, "selector-actions": { - "aria-label-selector": "", - "aria-label-selector-clear-button": "", - "aria-label-use-selector-as-metrics-button": "", - "aria-label-use-selector-for-query-button": "", - "aria-label-validate-submit-button": "", + "aria-label-selector": "Selektor", + "aria-label-selector-clear-button": "Löschen-Schaltfläche des Selektors", + "aria-label-use-selector-as-metrics-button": "Schaltfläche zur Verwendung des Selektors als Metrik", + "aria-label-use-selector-for-query-button": "Schaltfläche zur Verwendung des Selektors für die Abfrage", + "aria-label-validate-submit-button": "Validieren und Absenden-Schaltfläche", "clear": "Löschen", - "resulting-selector": "", - "use-as-rate-query": "", - "use-query": "", - "validate-selector": "" + "resulting-selector": "4. Resultierender Selektor", + "use-as-rate-query": "Als Bewertungsabfrage verwenden", + "use-query": "Abfrage verwenden", + "validate-selector": "Selektor validieren" }, "value-selector": { - "aria-label-filter-expression-for-label-values": "", - "aria-label-values-for": "", - "description-search-field-values-across-selected-labels": "", - "select-multiple-values-for-your-labels": "" + "aria-label-filter-expression-for-label-values": "Filterausdruck für Label-Werte", + "aria-label-values-for": "Werte für {{labelKey}}", + "description-search-field-values-across-selected-labels": "Verwenden Sie das Suchfeld, um Werte über mehrere ausgewählte Labels zu finden.", + "select-multiple-values-for-your-labels": "3. Wählen Sie (mehrere) Werte für Ihre Labels aus" } }, "configuration": { "alerting-settings-overhaul": { - "label-allow-as-recording-rules-target": "", - "label-manage-alerts-via-alerting-ui": "", + "label-allow-as-recording-rules-target": "Als Ziel für Aufnahmeregeln zulassen", + "label-manage-alerts-via-alerting-ui": "Benachrichtigungen über Alerting-UI verwalten", "title-alerting": "Meldungen", - "tooltip-allow-as-recording-rules-target": "", + "tooltip-allow-as-recording-rules-target": "Erlauben, dass diese Datenquelle als Ziel für das Schreiben von Aufnahmeregeln ausgewählt wird.", "tooltip-manage-alerts-via-alerting-ui": "Verwalten Sie Warnregeln für diese Datenquelle. Fügen Sie eine Altermanager-Datenquelle hinzu, um andere Warnressourcen zu verwalten." }, "config-editor": { - "browser-access-mode-error": "", - "description-advanced-settings": "", + "browser-access-mode-error": "Der Browserzugriffsmodus in der Prometheus-Datenquelle ist nicht mehr verfügbar. Wechseln Sie in den Serverzugriffsmodus.", + "description-advanced-settings": "Zusätzliche Einstellungen sind optionale Einstellungen, die für mehr Kontrolle über Ihre Datenquelle konfiguriert werden können.", "title-advanced-settings": "Erweiterte Einstellungen", "title-error": "Fehler" }, "data-source-http-settings-overhaul": { "tooltip-browser-access-mode": "Ihre Zugriffsmethode ist <1>Browser, d. h. die URL muss über den Browser zugänglich sein.", - "tooltip-http-url": "", + "tooltip-http-url": "Geben Sie eine vollständige HTTP-URL an (z. B. {{exampleURL}})", "tooltip-server-access-mode": "Ihre Zugriffsmethode ist <1>Server, d. h. die URL muss über das Grafana-Backend/den Grafana-Server zugänglich sein." }, "docs-tip": { - "visit-docs-for-more-details-here": "" + "visit-docs-for-more-details-here": "Weitere Details finden Sie hier in der Dokumentation." }, "exemplar-setting": { "label-data-source": "Datenquelle", - "label-internal-link": "", + "label-internal-link": "Interner Link", "label-label-name": "Label-Name", - "label-remove-exemplar-link": "", + "label-remove-exemplar-link": "Exemplar-Link entfernen", "label-url": "URL", - "label-url-label": "", - "placeholder-go-to-examplecom": "", - "placeholder-httpsexamplecomvalueraw": "", - "placeholder-trace-id": "", - "title-remove-exemplar-link": "", - "tooltip-data-source": "", - "tooltip-internal-link": "", - "tooltip-label-name": "", - "tooltip-url": "", - "tooltip-url-label": "" + "label-url-label": "URL-Label", + "placeholder-go-to-examplecom": "Zu example.com", + "placeholder-httpsexamplecomvalueraw": "https://example.com/${__value.raw}", + "placeholder-trace-id": "traceID", + "title-remove-exemplar-link": "Exemplar-Link entfernen", + "tooltip-data-source": "Die Datenquelle, zu der das Exemplar navigiert.", + "tooltip-internal-link": "Aktivieren Sie diese Option, wenn Sie einen internen Link haben. Wenn dies aktiviert ist, wird der Datenquellen-Selektor angezeigt. Wählen Sie den Backend-Tracing-Datenspeicher für Ihre Exemplar-Daten aus.", + "tooltip-label-name": "Der Name des Felds im Label-Objekt, das für den Erhalt der TraceID verwendet werden soll.", + "tooltip-url": "Die URL des Trace-Backends, die der Nutzer zur Ansicht des Trace aufrufen würde", + "tooltip-url-label": "Damit können Sie das Schaltflächen-Label im Feld der Exemplar-TraceID überschreiben." }, "exemplars-settings": { "add": "Hinzufügen", - "no-exemplars-configurations": "", - "title-exemplars": "" + "no-exemplars-configurations": "Keine Exemplar-Konfigurationen", + "title-exemplars": "Exemplare" }, "prom-settings": { - "aria-label-default-editor": "", - "aria-label-prom-type-type": "", - "aria-label-prometheus-type": "", - "aria-label-select-http-method": "", + "aria-label-default-editor": "Standard-Editor (Code oder Builder)", + "aria-label-prom-type-type": "{{promType}} Typ", + "aria-label-prometheus-type": "Prometheus-Typ", + "aria-label-select-http-method": "HTTP-Methode auswählen", "editor-options": { "label-builder": "", "label-code": "" }, - "label-cache-level": "", - "label-custom-query-parameters": "", - "label-default-editor": "", - "label-disable-metrics-lookup": "", - "label-disable-recording-rules-beta": "", - "label-http-method": "", - "label-incremental-querying-beta": "", - "label-metric-names-suggestion-limit": "", - "label-prom-type-version": "", - "label-prometheus-type": "", - "label-query-overlap-window": "", - "label-query-timeout": "", - "label-scrape-interval": "", - "label-series-limit": "", - "label-use-series-endpoint": "", - "more-info": "", - "placeholder-example-maxsourceresolutionmtimeout": "", - "title-interval-behaviour": "", - "title-other": "", + "label-cache-level": "Cache-Level", + "label-custom-query-parameters": "Benutzerdefinierte Abfrageparameter", + "label-default-editor": "Standard-Editor", + "label-disable-metrics-lookup": "Metrik-Suche deaktivieren", + "label-disable-recording-rules-beta": "Aufnahmeregeln deaktivieren (Beta)", + "label-http-method": "HTTP-Methode", + "label-incremental-querying-beta": "Inkrementelle Abfrage (Beta)", + "label-metric-names-suggestion-limit": "Begrenzung der Vorschläge für Metriknamen", + "label-prom-type-version": "{{promType}} Version", + "label-prometheus-type": "Prometheus-Typ", + "label-query-overlap-window": "Fenster der Abfrage-Überschneidung", + "label-query-timeout": "Abfrage-Timeout", + "label-scrape-interval": "Scrape-Intervall", + "label-series-limit": "Reihengrenze", + "label-use-series-endpoint": "Reihenendpunkt verwenden", + "more-info": "Weitere Informationen zur Konfiguration des Typs und der Version von Prometheus in Datenquellen finden Sie in der <2>Bereitstellungsdokumentation.", + "placeholder-example-maxsourceresolutionmtimeout": "Beispiel: {{example}}", + "title-interval-behaviour": "Intervallverhalten", + "title-other": "Sonstiges", "title-performance": "Leistung", - "title-query-editor": "", - "tooltip-cache-level": "", - "tooltip-custom-query-parameters": "", - "tooltip-default-editor": "", - "tooltip-disable-metrics-lookup": "", - "tooltip-disable-recording-rules-beta": "", - "tooltip-http-method": "", - "tooltip-incremental-querying-beta": "", - "tooltip-metric-names-suggestion-limit": "", - "tooltip-prom-type-version": "", - "tooltip-prometheus-type": "", - "tooltip-query-overlap-window": "", - "tooltip-query-timeout": "", - "tooltip-scrape-interval": "", - "tooltip-series-limit": "", - "tooltip-use-series-endpoint": "" + "title-query-editor": "Abfrage-Editor", + "tooltip-cache-level": "Legt die Browser-Cache-Level für Editor-Abfragen fest. Für Datenquellen mit hoher Kardinalität werden höhere Cache-Einstellungen empfohlen.", + "tooltip-custom-query-parameters": "Fügen Sie der URL der Prometheus-Abfrage benutzerdefinierte Parameter hinzu. Zum Beispiel {{example1}}, {{example2}}, {{example3}} oder {{example4}}. Mehrere Parameter sollten mit {{concatenationChar}} verknüpft werden.", + "tooltip-default-editor": "Legen Sie die Standard-Editoroption für alle Nutzer dieser Datenquelle fest.", + "tooltip-disable-metrics-lookup": "Wenn diese Option aktiviert ist, werden die Metrikauswahl und die Unterstützung für Metriken/Labels im Rahmen der Autovervollständigung des Abfragefelds deaktiviert. Dies hilft, wenn Sie Leistungsprobleme mit größeren Prometheus-Instanzen haben. ", + "tooltip-disable-recording-rules-beta": "Diese Funktion deaktiviert die Aufnahmeregeln. Wenn Sie dies aktivieren, wird die Dashboard-Leistung verbessert", + "tooltip-http-method": "Sie können entweder die HTTP-Methode POST oder GET für die Abfrage Ihrer Prometheus-Datenquelle verwenden. POST ist die empfohlene Methode, da sie größere Abfragen ermöglicht. Ändern Sie dies zu GET, wenn Ihre Prometheus-Version älter als 2.1 ist oder POST-Abfragen in Ihrem Netzwerk beschränkt sind.", + "tooltip-incremental-querying-beta": "Diese Funktion ändert das Standardverhalten von relativen Abfragen, sodass immer neue Daten von der Prometheus-Instanz abgefragt werden. Stattdessen werden Abfrageergebnisse zwischengespeichert und nur neue Datensätze abgefragt. Wenn Sie dies aktivieren, wird die Datenbank- und Netzwerklast reduziert.", + "tooltip-metric-names-suggestion-limit": "Die maximale Anzahl an Metriknamen, die im Code-Modus des Abfrage-Editors als Vorschläge im Rahmen der Autovervollständigung angezeigt werden können.", + "tooltip-prom-type-version": "Nutzen Sie diese Option, um die Version Ihrer Instanz von {{promType}} festzulegen, wenn sie nicht automatisch konfiguriert wird.", + "tooltip-prometheus-type": "Stellen Sie dies auf den Typ Ihrer Prometheus-Datenbank ein, z. B. Prometheus, Cortex, Mimir oder Thanos. Wenn Sie dieses Feld ändern, werden Ihre aktuellen Einstellungen gespeichert. Bestimmte Typen von Prometheus unterstützen entweder verschiedene APIs oder nicht. Zum Beispiel unterstützen manche Typen Regex-Matching für Label-Abfragen, um die Leistung zu verbessern. Manche Typen haben eine API für Metadaten. Wenn Sie dies falsch einstellen, kann es bei der Abfrage von Metriken und Labels zu einem ungewöhnlichen Verhalten kommen. Bitte prüfen Sie Ihre Prometheus-Dokumentation, um sicherzugehen, dass Sie den richtigen Typ eingeben.", + "tooltip-query-overlap-window": "Stellen Sie eine Dauer ein, zum Beispiel {{example1}} oder {{example2}} oder {{example3}}. Standardwert: {{default}}. Diese Dauer wird zur Dauer jeder inkrementellen Abfrage hinzugefügt.", + "tooltip-query-timeout": "Legen Sie das Prometheus-Abfrage-Timeout fest.", + "tooltip-scrape-interval": "Dieses Intervall gibt an, wie oft Prometheus ein Scraping für Ziele durchführt. Stellen Sie dies auf das typische Scrape- und Bewertungsintervall ein, das in Ihrer Prometheus-Konfigurationsdatei konfiguriert ist. Wenn Sie dies auf einen größeren Wert als das Intervall Ihrer Prometheus-Konfigurationsdatei einstellen, wertet Grafana die Daten gemäß diesem Intervall aus und Sie sehen weniger Datenpunkte. Standardmäßig {{default}}.", + "tooltip-series-limit": "Die Grenze gilt für alle Ressourcen (Metriken, Labels und Werte) für beide Endpunkte (Reihen und Labels). Wenn Sie das Feld leer lassen, wird die Standardgrenze verwendet (40.000). Durch eine Einstellung auf 0 wird die Grenze deaktiviert und alles abgerufen – dies kann zu Leistungsproblemen führen. Die Standardgrenze beträgt 40.000.", + "tooltip-use-series-endpoint": "Wenn diese Option aktiviert ist, wird der Reihenendpunkt mit dem Parameter {{exampleParameter}} gegenüber dem Endpunkt der Label-Werte mit dem Parameter {{exampleParameter}} bevorzugt. Obwohl der Endpunkt der Label-Werte als leistungsfähiger gilt, könnten manche Nutzer die Reihe bevorzugen, da sie über eine POST-Methode verfügt, während der Endpunkt der Label-Werte nur über eine GET-Methode verfügt." } }, "prom-query-legend-editor": { @@ -209,14 +209,111 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { - "content-filter-metric-names-regex-search-using": "", - "disable-text-wrap": "" + "content-filter-metric-names-regex-search-using": "Filtern Sie Metriknamen per Regex-Suche und nutzen Sie dabei einen zusätzlichen Aufruf der Prometheus-API.", + "disable-text-wrap": "Textumbruch deaktivieren" }, "feedback-link": { "give-feedback": "Feedback geben", - "title-give-feedback": "" + "title-give-feedback": "Der Metrik-Explorer ist neu. Bitte teilen Sie uns mit, wie wir ihn verbessern können." }, "get-collapsed-info": { "exemplars": "", @@ -250,18 +347,18 @@ }, "handle-function": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "Das Abfrage-Parsing ist unklar." } }, "label-filter-item": { - "aria-label-remove": "", - "placeholder-select-label": "", + "aria-label-remove": "Entfernen von {{name}}", + "placeholder-select-label": "Label auswählen", "placeholder-select-value": "Wert auswählen" }, "label-filters": { - "label-filters": "", - "label-label-filters": "", - "tooltip-label-filters": "" + "label-filters": "Label-Filter", + "label-label-filters": "Label-Filter", + "tooltip-label-filters": "Optional: wird verwendet, um die Metrikauswahl für diesen Abfragetyp zu filtern." }, "label-param-editor": { "loadingMessage-loading-labels": "", @@ -269,92 +366,99 @@ }, "metric-combobox": { "async-select": { - "aria-label-open-metrics-explorer": "", - "placeholder-select-metric": "", - "tooltip-open-metrics-explorer": "" + "aria-label-open-metrics-explorer": "Metrik-Explorer öffnen", + "placeholder-select-metric": "Metrik auswählen", + "tooltip-open-metrics-explorer": "Metrik-Explorer öffnen" }, "label-metric": "Metrik", - "tooltip-metric": "" + "tooltip-metric": "Optional: gibt eine Liste von Label-Werten für den Label-Namen in der angegebenen Metrik zurück." }, "metrics-modal": { - "additional-settings": "", + "additional-settings": "Zusätzliche Einstellungen", "aria-label-additional-settings": "Zusätzliche Einstellungen", - "aria-label-browse-metrics": "", - "currently-selected": "", - "metrics-pre-filtered": "", - "placeholder-results-per-page": "", - "results-per-page": "", - "title-metrics-explorer": "", - "results-amount_one": "", - "results-amount_other": "" + "aria-label-browse-metrics": "Metriken durchsuchen", + "currently-selected": "Aktuell ausgewählt: {{selected}}", + "metrics-pre-filtered": "Diese Metriken wurden anhand der Labels, die in den Label-Filtern ausgewählt wurden, vorgefiltert.", + "placeholder-results-per-page": "Ergebnisse pro Seite", + "results-per-page": "Ergebnisse pro Seite", + "title-metrics-explorer": "Metrik-Explorer", + "results-amount_one": "{{num}} von {{count}} Ergebnissen werden angezeigt", + "results-amount_other": "{{num}} von {{count}} Ergebnissen werden angezeigt" }, "nested-query": { "label": { - "ignoring": "", + "ignoring": "Ignorieren", "on": "Aktiviert" }, "operator": "Bediener", - "tooltip-remove-match": "", - "vector-matches": "" + "tooltip-remove-match": "Übereinstimmung entfernen", + "vector-matches": "Vektorübereinstimmungen" }, "operation-editor": { - "not-found": "", - "title-remove": "" + "not-found": "Vorgang {{id}} nicht gefunden", + "title-remove": "Entfernen von {{name}}" }, "operation-header": { "placeholder-replace-with": "Ersetzen mit", - "title-click-to-view-alternative-operations": "", - "title-remove-operation": "" + "title-click-to-view-alternative-operations": "Klicken Sie zur Anzeige alternativer Vorgänge", + "title-remove-operation": "Vorgang entfernen" }, "operation-info-button": { - "title-click-to-show-description": "", - "title-remove-operation": "" + "title-click-to-show-description": "Klicken Sie zur Ansicht der Beschreibung", + "title-remove-operation": "Operation entfernen" }, "operation-list": { - "operations": "", + "operations": "Vorgänge", "placeholder-search": "Suche", - "title-add-operation": "" + "title-add-operation": "Vorgang hinzufügen" }, "operation-param-editor": { - "title-add": "", - "title-remove": "" + "title-add": "{{name}} hinzufügen", + "title-remove": "{{name}} entfernen" + }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } }, "prom-query-builder-options": { - "aria-label-lower-limit-parameter": "", - "aria-label-select-resolution": "", + "aria-label-lower-limit-parameter": "Untergrenze für den Schrittparameter festlegen", + "aria-label-select-resolution": "Auflösung auswählen", "format-options": { "label-heatmap": "", "label-table": "", "label-time-series": "" }, - "label-exemplars": "", + "label-exemplars": "Exemplare", "label-format": "Format", - "label-min-step": "", - "label-resolution": "", + "label-min-step": "Min. Schritt", + "label-resolution": "Auflösung", "label-type": "Typ", - "placeholder-auto": "", + "placeholder-auto": "Auto", "title-options": "Optionen", - "tooltip-min-step": "" + "tooltip-min-step": "Eine zusätzliche Untergrenze für den Schrittparameter der Prometheus-Abfrage und für die Variablen <2>{{interval}} und <4>{{rateInterval}}." }, "prom-query-code-editor-autocomplete-info": { - "autocomplete-suggestions-limited": "", - "tooltip-autocomplete-suggestions-limited": "" + "autocomplete-suggestions-limited": "Vorschläge der Autovervollständigung begrenzt", + "tooltip-autocomplete-suggestions-limited": "Die Anzahl der Metriknamen überschreitet die Grenze der Autovervollständigung. Es werden nur die für {{autocompleteLimit}} relevantesten Metriken angezeigt. Sie können die Grenze in den Datenquelleneinstellungen anpassen." }, "prom-query-editor-selector": { "body-syntax-error": "", "confirmText-continue": "", "kick-start-your-query": "Starten Sie Ihre Abfrage", - "label-explain": "", - "run-queries": "", - "title-parsing-error-switch-builder": "" + "label-explain": "Erklären", + "run-queries": "Abfragen ausführen", + "title-parsing-error-switch-builder": "Parsing-Fehler: In den Builder-Modus wechseln?" }, "prom-query-legend-editor": { - "label-legend": "", - "placeholder-select-legend-mode": "", - "tooltip-legend": "" + "label-legend": "Legende", + "placeholder-select-legend-mode": "Legendenmodus auswählen", + "tooltip-legend": "Überschreibung des Reihennamens oder Vorlage. Beispiel: {{templateExample}} wird durch den Label-Wert für {{labelName}} ersetzt." }, "query-builder-hints": { - "hint-details": "" + "hint-details": "Hinweis: {{hintDetails}}" }, "query-editor-mode-toggle": { "editor-modes": { @@ -363,30 +467,30 @@ } }, "query-pattern": { - "apply-query": "", - "aria-label-apply-query-starter-button": "", - "aria-label-back-button": "", - "aria-label-create-new-query-button": "", - "aria-label-raw-query": "", - "aria-label-use-this-query-button": "", + "apply-query": "Abfrage anwenden", + "aria-label-apply-query-starter-button": "Schaltfläche zur Anwendung des Abfrage-Starters", + "aria-label-back-button": "Zurück-Schaltfläche", + "aria-label-create-new-query-button": "Schaltfläche zur Erstellung einer neuen Abfrage", + "aria-label-raw-query": "{{patternName}} Rohabfrage", + "aria-label-use-this-query-button": "Schaltfläche zum Nutzen dieser Abfrage", "back": "Zurück", - "create-new-query": "", + "create-new-query": "Neue Abfrage erstellen", "use-this-query": "Diese Abfrage nutzen" }, "query-patterns-modal": { - "aria-label-close-kick-start-your-query-modal": "", + "aria-label-close-kick-start-your-query-modal": "Modal zum Starten Ihrer Abfrage schließen", "aria-label-kick-start-your-query-modal": "Starten Sie Ihr Abfrage-Modal", - "aria-label-toggle-query-starter": "", + "aria-label-toggle-query-starter": "Öffnen und Schließen der {{patternType}} Abfrage-Starter-Karte", "close": "Schließen", - "description-kick-start-your-query": "", - "label-toggle-query-starter": "", + "description-kick-start-your-query": "Starten Sie Ihre Abfrage, indem Sie eine dieser Abfragen auswählen. Sie können dann mit dem Abschluss Ihrer Abfrage fortfahren.", + "label-toggle-query-starter": "{{patternType}} Abfrage-Starter", "title-kick-start-your-query": "Starten Sie Ihre Abfrage" }, "raw-query": { - "aria-label-selector": "" + "aria-label-selector": "Selektor" }, "results-table": { - "content-descriptive-type": "", + "content-descriptive-type": "Bei der Erstellung von {{descriptiveType}} zeigt Prometheus mehrere Reihen mit dem Typ Counter. ", "description": "Beschreibung", "message-expand-label-filters": "", "message-expand-search": "", @@ -397,7 +501,7 @@ }, "update-function-args": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "Das Abfrage-Parsing ist unklar." } } } diff --git a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json index 26fecb50168..85eba24f8a4 100644 --- a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "Verbose" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "Calculate the average over dimensions", + "documentation-bottomk": "Smallest k elements by sample value", + "documentation-count": "Count number of elements in the vector", + "documentation-count-values": "Count number of elements with the same value", + "documentation-group": "All values in the resulting vector are 1", + "documentation-max": "Select maximum over dimensions", + "documentation-min": "Select minimum over dimensions", + "documentation-quantile": "Calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions", + "documentation-stddev": "Calculate population standard deviation over dimensions", + "documentation-stdvar": "Calculate population standard variance over dimensions", + "documentation-sum": "Calculate sum over dimensions", + "documentation-topk": "Largest k elements by sample value" + }, + "getFunctions": { + "documentation-abs": "Returns the input vector with all sample values converted to their absolute value.", + "documentation-absent": "Returns an empty vector if the vector passed to it has any elements and a 1-element vector with the value 1 if the vector passed to it has no elements. This is useful for alerting on when no time series exist for a given metric name and label combination.", + "documentation-absent-over-time": "Returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements.", + "documentation-avg-over-time": "The average value of all points in the specified interval.", + "documentation-ceil": "Rounds the sample values of all elements in `v` up to the nearest integer.", + "documentation-changes": "For each input time series, `changes(v range-vector)` returns the number of times its value has changed within the provided time range as an instant vector.", + "documentation-clamp": "Clamps the sample values of all elements in `v` to have a lower limit of `min` and an upper limit of `max`.", + "documentation-clamp-max": "Clamps the sample values of all elements in `v` to have an upper limit of `max`.", + "documentation-clamp-min": "Clamps the sample values of all elements in `v` to have a lower limit of `min`.", + "documentation-count-over-time": "The count of all values in the specified interval.", + "documentation-count-scalar": "Returns the number of elements in a time series vector as a scalar. This is in contrast to the `count()` aggregation operator, which always returns a vector (an empty one if the input vector is empty) and allows grouping by labels via a `by` clause.", + "documentation-day-of-month": "Returns the day of the month for each of the given times in UTC. Returned values are from 1 to 31.", + "documentation-day-of-week": "Returns the day of the week for each of the given times in UTC. Returned values are from 0 to 6, where 0 means Sunday etc.", + "documentation-day-of-year": "Returns the day of the year for each of the given times in UTC. Returned values are from 1 to 365 for non-leap years, and 1 to 366 in leap years.", + "documentation-days-in-month": "Returns number of days in the month for each of the given times in UTC. Returned values are from 28 to 31.", + "documentation-deg": "Converts radians to degrees for all elements in v", + "documentation-delta": "Calculates the difference between the first and last value of each time series element in a range vector `v`, returning an instant vector with the given deltas and equivalent labels. The delta is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if the sample values are all integers.", + "documentation-deriv": "Calculates the per-second derivative of the time series in a range vector `v`, using simple linear regression.", + "documentation-double-exponential-smoothing": "Produces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.", + "documentation-drop-common-labels": "Drops all labels that have the same name and value across all series in the input vector.", + "documentation-exp": "Calculates the exponential function for all elements in `v`.\nSpecial cases are:\n* `Exp(+Inf) = +Inf` \n* `Exp(NaN) = NaN`", + "documentation-floor": "Rounds the sample values of all elements in `v` down to the nearest integer.", + "documentation-histogram-avg": "Returns the arithmetic average of observed values stored in a native histogram. Samples that are not native histograms are ignored and do not show up in the returned vector.", + "documentation-histogram-count": "Returns the count of observations stored in a native histogram.", + "documentation-histogram-fraction": "Returns the estimated fraction of observations between the provided lower and upper values.", + "documentation-histogram-quantile": "Calculates the φ-quantile (0 ≤ φ ≤ 1) from the buckets `b` of a histogram. The samples in `b` are the counts of observations in each bucket. Each sample must have a label `le` where the label value denotes the inclusive upper bound of the bucket. (Samples without such a label are silently ignored.) The histogram metric type automatically provides time series with the `_bucket` suffix and the appropriate labels.", + "documentation-histogram-stddev": "Returns the estimated standard deviation of observations in a native histogram, based on the geometric mean of the buckets where the observations lie.", + "documentation-histogram-stdvar": "Returns the estimated standard variance of observations in a native histogram.", + "documentation-histogram-sum": "Returns the sum of observations stored in a native histogram.", + "documentation-holt-winters": "Renamed as double_exponential_smoothing in prometheus v3.x. For prometheus versions equal and greater than v3.0 please use double_exponential_smoothing. \n\nProduces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.", + "documentation-hour": "Returns the hour of the day for each of the given times in UTC. Returned values are from 0 to 23.", + "documentation-idelta": "Calculates the difference between the last two samples in the range vector `v`, returning an instant vector with the given deltas and equivalent labels.", + "documentation-increase": "Calculates the increase in the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments.", + "documentation-info": "Returns latest details and metadata about a group of metrics, such as their labels and current values, without doing any calculations", + "documentation-irate": "Calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for.", + "documentation-label-join": "For each timeseries in `v`, joins all the values of all the `src_labels` using `separator` and returns the timeseries with the label `dst_label` containing the joined value. There can be any number of `src_labels` in this function.", + "documentation-label-replace": "For each timeseries in `v`, `label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)` matches the regular expression `regex` against the label `src_label`. If it matches, then the timeseries is returned with the label `dst_label` replaced by the expansion of `replacement`. `$1` is replaced with the first matching subgroup, `$2` with the second etc. If the regular expression doesn't match then the timeseries is returned unchanged.", + "documentation-last-over-time": "The most recent point value in specified interval.", + "documentation-ln": "Calculates the natural logarithm for all elements in `v`.\nSpecial cases are:\n * `ln(+Inf) = +Inf`\n * `ln(0) = -Inf`\n * `ln(x < 0) = NaN`\n * `ln(NaN) = NaN`", + "documentation-log10": "Calculates the decimal logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.", + "documentation-log2": "Calculates the binary logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.", + "documentation-max-over-time": "The maximum value of all points in the specified interval.", + "documentation-min-over-time": "The minimum value of all points in the specified interval.", + "documentation-minute": "Returns the minute of the hour for each of the given times in UTC. Returned values are from 0 to 59.", + "documentation-month": "Returns the month of the year for each of the given times in UTC. Returned values are from 1 to 12, where 1 means January etc.", + "documentation-pi": "Returns pi", + "documentation-predict-linear": "Predicts the value of time series `t` seconds from now, based on the range vector `v`, using simple linear regression.", + "documentation-present-over-time": "The value 1 for any series in the specified interval.", + "documentation-quantile-over-time": "The φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.", + "documentation-rad": "Converts degrees to radians for all elements in v", + "documentation-rate": "Calculates the per-second average rate of increase of the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. Also, the calculation extrapolates to the ends of the time range, allowing for missed scrapes or imperfect alignment of scrape cycles with the range's time period.", + "documentation-resets": "For each input time series, `resets(v range-vector)` returns the number of counter resets within the provided time range as an instant vector. Any decrease in the value between two consecutive samples is interpreted as a counter reset.", + "documentation-round": "Rounds the sample values of all elements in `v` to the nearest integer. Ties are resolved by rounding up. The optional `to_nearest` argument allows specifying the nearest multiple to which the sample values should be rounded. This multiple may also be a fraction.", + "documentation-scalar": "Given a single-element input vector, `scalar(v instant-vector)` returns the sample value of that single element as a scalar. If the input vector does not have exactly one element, `scalar` will return `NaN`.", + "documentation-sgn": "Returns a vector with all sample values converted to their sign, defined as this: 1 if v is positive, -1 if v is negative and 0 if v is equal to zero.", + "documentation-sort": "Returns vector elements sorted by their sample values, in ascending order.", + "documentation-sort-desc": "Returns vector elements sorted by their sample values, in descending order.", + "documentation-sqrt": "Calculates the square root of all elements in `v`.", + "documentation-stddev-over-time": "The population standard deviation of the values in the specified interval.", + "documentation-stdvar-over-time": "The population standard variance of the values in the specified interval.", + "documentation-sum-over-time": "The sum of all values in the specified interval.", + "documentation-time": "Returns the number of seconds since January 1, 1970 UTC. Note that this does not actually return the current time, but the time at which the expression is to be evaluated.", + "documentation-timestamp": "Returns the timestamp of each of the samples of the given vector as the number of seconds since January 1, 1970 UTC.", + "documentation-vector": "Returns the scalar `s` as a vector with no labels.", + "documentation-year": "Returns the year for each of the given times in UTC." + }, + "getTrigonometricFunctions": { + "documentation-acos": "calculates the arccosine of all elements in {{argument}}", + "documentation-acosh": "calculates the inverse hyperbolic cosine of all elements in {{argument}}", + "documentation-asin": "calculates the arcsine of all elements in {{argument}}", + "documentation-asinh": "calculates the inverse hyperbolic sine of all elements in {{argument}}", + "documentation-atan": "calculates the arctangent of all elements in {{argument}}", + "documentation-atanh": "calculates the inverse hyperbolic tangent of all elements in {{argument}}", + "documentation-cos": "calculates the cosine of all elements in {{argument}}", + "documentation-cosh": "calculates the hyperbolic cosine of all elements in {{argument}}", + "documentation-sin": "calculates the sine of all elements in {{argument}}", + "documentation-sinh": "calculates the hyperbolic sine of all elements in {{argument}}", + "documentation-tan": "calculates the tangent of all elements in {{argument}}", + "documentation-tanh": "calculates the hyperbolic tangent of all elements in {{argument}}" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "Filter metric names by regex search, using an additional call on the Prometheus API.", @@ -319,6 +416,13 @@ "title-add": "Add {{name}}", "title-remove": "Remove {{name}}" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "Calculates {{aggregationName}} over dimensions while preserving {{labelWord}} {{labels}}.", + "label-default": "Calculates {{aggregationName}} over the dimensions.", + "label-without": "Calculates {{aggregationName}} over the dimensions {{labels}}. All other labels are preserved." + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "Set lower limit for the step parameter", "aria-label-select-resolution": "Select resolution", diff --git a/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json index d4ca0b7b366..fc668327cdb 100644 --- a/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json index 18813255785..409e95dcf48 100644 --- a/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "Filtrez les noms de métriques par recherche d’expression régulière, en utilisant un appel supplémentaire sur l’API Prometheus.", @@ -319,6 +416,13 @@ "title-add": "Ajouter {{name}}", "title-remove": "Supprimer {{name}}" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "Définir la limite inférieure pour le paramètre d’étape", "aria-label-select-resolution": "Sélectionner une résolution", diff --git a/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json index 80848ade77a..22bc5c9c64b 100644 --- a/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json @@ -2,201 +2,201 @@ "grafana-prometheus": { "components": { "annotation-query-editor": { - "annotation-data-load-error": "", - "aria-label-lower-limit-parameter": "", - "label-min-step": "", - "label-series-value-as-timestamp": "", + "annotation-data-load-error": "Hiba a jegyzetadatok betöltése során.", + "aria-label-lower-limit-parameter": "Állítsa be a léptékparaméter alsó határát", + "label-min-step": "Min. lépték", + "label-series-value-as-timestamp": "Sorozatérték időbélyegként", "label-tags": "Címkék", - "label-text": "", + "label-text": "Szöveg", "label-title": "Cím", - "placeholder-auto": "", - "tooltip-either-pattern-example-instance-replaced-label": "", - "tooltip-min-step": "", - "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "" + "placeholder-auto": "automatikus", + "tooltip-either-pattern-example-instance-replaced-label": "Használja a nevet vagy egy mintázatot. Például a(z) {{labelTemplate}} helyébe a(z) {{labelName}} címke címkeértéke lép.", + "tooltip-min-step": "További alsó határérték a Prometheus-lekérdezés lépésparaméteréhez, valamint a(z) <2>{{intervalVar}} és <4>{{rateIntervalVar}} változóhoz.", + "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "Az időbélyeg mértékegysége az ezredmásodperc. Ha a sorozatérték mértékegysége a másodperc, szorozza meg a tartományvektorát 1000-rel." }, "get-query-type-options": { "description": { - "instant-query-range": "" + "instant-query-range": "Azonnali lekérdezés és tartománylekérdezés futtatása" }, "label": { - "both": "" + "both": "Mindkettő" }, "range-options": { "description": { - "query-range": "" + "query-range": "Lekérdezés futtatása egy időtartományban" }, "label": { - "instant": "", - "range": "" + "instant": "Azonnali", + "range": "Tartomány" } } }, "label-selector": { - "aria-label-filter-expression-for-label": "", - "description-select-labels": "", - "select-labels-to-search-in": "" + "aria-label-filter-expression-for-label": "Szűrőkifejezés címkéhez", + "description-select-labels": "A címkeértékek kiválasztása után csak a lehetséges címkekombinációk jelennek meg.", + "select-labels-to-search-in": "2. Válassza ki a kereséshez használni kívánt címkéket" }, "metric-selector": { - "aria-label-filter-expression-for-metric": "", - "aria-label-limit-results-from-series-endpoint": "", - "description-series-limit": "", - "label-select-metric": "", - "select-a-metric": "", - "series-limit": "" + "aria-label-filter-expression-for-metric": "Szűrőkifejezés a metrikához", + "aria-label-limit-results-from-series-endpoint": "A sorozat végpontjáról származó eredmények korlátozása", + "description-series-limit": "A korlát minden metrikára, címkére és értékre vonatkozik. Hagyja üresen a mezőt az alapértelmezett korlát használatához. Állítsa 0-ra a korlát letiltásához és az összes lekéréséhez – ez teljesítményproblémákat okozhat.", + "label-select-metric": "A metrika kiválasztása után csak a lehetséges címkék jelennek meg. A címkéket az alábbi sorozatkorlát korlátozza.", + "select-a-metric": "1. Válasszon egy metrikát", + "series-limit": "Sorozatkorlát" }, "prom-cheat-sheet": { - "prom-ql-cheat-sheet": "" + "prom-ql-cheat-sheet": "PromQL-puska" }, "prom-exemplar-field": { - "exemplars": "", - "tooltip-disable-query": "", - "tooltip-enable-query": "" + "exemplars": "Mintapéldányok", + "tooltip-disable-query": "Mintapéldányokkal rendelkező lekérdezések letiltása", + "tooltip-enable-query": "Mintapéldányokkal rendelkező lekérdezések engedélyezése" }, "prom-explore-extra-field": { - "aria-label-prometheus-extra-field": "", - "aria-label-query-type-field": "", - "aria-label-step-field": "", - "min-step": "", - "query-type": "", - "tooltip-units-builtin-variables-example-interval-rateinterval": "" + "aria-label-prometheus-extra-field": "Prometheus extra mező", + "aria-label-query-type-field": "Lekérdezéstípus mező", + "aria-label-step-field": "Lépték mező", + "min-step": "Min. lépték", + "query-type": "Lekérdezés típusa", + "tooltip-units-builtin-variables-example-interval-rateinterval": "Itt időegységek és beépített változók használhatók, például: {{example1}}, {{example2}}, {{example3}}, {{example4}}, {{example5}}, {{example6}}, {{example7}} (alapértelmezett, ha nincs megadva mértékegység: {{default}})" }, "prom-query-field": { - "placeholder-enter-a-prom-ql-query": "" + "placeholder-enter-a-prom-ql-query": "Írjon be egy PromQL-lekérdezést…" }, "prom-variable-query-editor": { - "aria-label-classic-query": "", - "aria-label-metric-regex": "", - "aria-label-metric-selector": "", - "aria-label-prometheus-query": "", - "aria-label-query-type": "", - "aria-label-series-query": "", - "label-classic-query": "", + "aria-label-classic-query": "Klasszikus lekérdezés", + "aria-label-metric-regex": "Metrika reguláris kifejezése", + "aria-label-metric-selector": "Metrikaválasztó", + "aria-label-prometheus-query": "Prometheus-lekérdezés", + "aria-label-query-type": "Lekérdezés típusa", + "aria-label-series-query": "Sorozatlekérdezés", + "label-classic-query": "Klasszikus lekérdezés", "label-label": "Címke", - "label-metric-regex": "", + "label-metric-regex": "Metrika reguláris kifejezése", "label-query": "Lekérdezés", - "label-query-type": "", - "label-series-query": "", - "placeholder-classic-query": "", - "placeholder-metric-regex": "", - "placeholder-prometheus-query": "", + "label-query-type": "Lekérdezés típusa", + "label-series-query": "Sorozatlekérdezés", + "placeholder-classic-query": "Klasszikus lekérdezés", + "placeholder-metric-regex": "Metrika reguláris kifejezése", + "placeholder-prometheus-query": "Prometheus-lekérdezés", "placeholder-select-query-type": "Lekérdezéstípus kiválasztása", - "placeholder-series-query": "", - "returns-metrics-matching-specified-metric-regex": "", - "tooltip-classic-query": "", - "tooltip-label": "", - "tooltip-metric-regex": "", - "tooltip-query": "", - "tooltip-query-type": "", - "tooltip-series-query": "" + "placeholder-series-query": "Sorozatlekérdezés", + "returns-metrics-matching-specified-metric-regex": "A megadott metrikai reguláris kifejezésnek megfelelő metrikák listáját adja vissza.", + "tooltip-classic-query": "A Prometheus változólekérdezés-szerkesztő eredeti implementációja. Adjon meg egy karakterláncot a megfelelő lekérdezéstípussal és paraméterekkel, a jelen dokumentumokban leírtak szerint. Például: {{exampleQuery}}.", + "tooltip-label": "A címke nevéhez tartozó címkeértékek listáját adja vissza az összes metrikában, kivéve, ha a metrika meg van adva.", + "tooltip-metric-regex": "A címkenevek listáját adja vissza, opcionálisan a megadott metrikai reguláris kifejezés szerint szűrve.", + "tooltip-query": "A lekérdezéshez a Prometheus-lekérdezési eredmények listáját adja vissza. Ez magában foglalhatja a Prometheus-funkciókat, azaz {{exampleQuery}}.", + "tooltip-query-type": "A Prometheus adatforrás-beépülőmodul a következő lekérdezéstípusokat biztosítja a sablonváltozókhoz.", + "tooltip-series-query": "Adjon meg egy metrikát címkékkel, csak egy metrikát vagy csak címkéket: {{example1}}, {{example2}} vagy {{example3}}. A bevitt adatokhoz társított idősorok listáját adja vissza." }, "selector-actions": { - "aria-label-selector": "", - "aria-label-selector-clear-button": "", - "aria-label-use-selector-as-metrics-button": "", - "aria-label-use-selector-for-query-button": "", - "aria-label-validate-submit-button": "", + "aria-label-selector": "választó", + "aria-label-selector-clear-button": "Választó törlése gomb", + "aria-label-use-selector-as-metrics-button": "Választó használata metrikaként gomb", + "aria-label-use-selector-for-query-button": "Választó használata lekérdezéshez gomb", + "aria-label-validate-submit-button": "Küldés érvényesítése gomb", "clear": "Törlés", - "resulting-selector": "", - "use-as-rate-query": "", - "use-query": "", - "validate-selector": "" + "resulting-selector": "4. Eredményként kapott választó", + "use-as-rate-query": "Használat értékeléses lekérdezésként", + "use-query": "Lekérdezés használata", + "validate-selector": "Választó érvényesítése" }, "value-selector": { - "aria-label-filter-expression-for-label-values": "", - "aria-label-values-for": "", - "description-search-field-values-across-selected-labels": "", - "select-multiple-values-for-your-labels": "" + "aria-label-filter-expression-for-label-values": "Szűrőkifejezés címkeértékekhez", + "aria-label-values-for": "Értékek a következőhöz: {{labelKey}}", + "description-search-field-values-across-selected-labels": "A keresőmező segítségével kereshet értékeket a kiválasztott címkék között.", + "select-multiple-values-for-your-labels": "3. Válasszon (több) értéket a címkéihez" } }, "configuration": { "alerting-settings-overhaul": { - "label-allow-as-recording-rules-target": "", - "label-manage-alerts-via-alerting-ui": "", + "label-allow-as-recording-rules-target": "Engedélyezés felvételkészítési szabályok céljaként", + "label-manage-alerts-via-alerting-ui": "Riasztások kezelése az Alerting kezelőfelületén keresztül", "title-alerting": "Riasztás", - "tooltip-allow-as-recording-rules-target": "", + "tooltip-allow-as-recording-rules-target": "Engedélyezze, hogy ez az adatforrás célként legyen kiválasztva a felvételkészítési szabályok írásához.", "tooltip-manage-alerts-via-alerting-ui": "Riasztási szabályok kezelése ehhez az adatforráshoz. Más Alerting-erőforrások kezeléséhez adjon hozzá egy Alertmanager-adatforrást." }, "config-editor": { - "browser-access-mode-error": "", - "description-advanced-settings": "", + "browser-access-mode-error": "A Prometheus-adatforrás böngészős hozzáférési módja már nem érhető el. Váltás kiszolgáló-hozzáférési módra.", + "description-advanced-settings": "A további beállítások olyan opcionális beállítások, amelyek konfigurálhatók az adatforrás nagyobb mértékű ellenőrzése érdekében.", "title-advanced-settings": "Speciális beállítások", "title-error": "Hiba" }, "data-source-http-settings-overhaul": { "tooltip-browser-access-mode": "Az Ön hozzáférési módja <1>Böngésző, ez azt jelenti, hogy az URL-címnek elérhetőnek kell lennie a böngészőből.", - "tooltip-http-url": "", + "tooltip-http-url": "Adjon meg egy teljes HTTP URL-címet (például: {{exampleURL}})", "tooltip-server-access-mode": "Az Ön hozzáférési módja <1>Kiszolgáló, ez azt jelenti, hogy az URL-címnek elérhetőnek kell lennie a Grafana-háttérrendszerből/-kiszolgálóról." }, "docs-tip": { - "visit-docs-for-more-details-here": "" + "visit-docs-for-more-details-here": "További részletekért tekintse meg a dokumentumokat itt." }, "exemplar-setting": { "label-data-source": "Adatforrás", - "label-internal-link": "", + "label-internal-link": "Belső hivatkozás", "label-label-name": "Címkenév", - "label-remove-exemplar-link": "", + "label-remove-exemplar-link": "Mintapéldány-hivatkozás eltávolítása", "label-url": "URL-cím", - "label-url-label": "", - "placeholder-go-to-examplecom": "", - "placeholder-httpsexamplecomvalueraw": "", - "placeholder-trace-id": "", - "title-remove-exemplar-link": "", - "tooltip-data-source": "", - "tooltip-internal-link": "", - "tooltip-label-name": "", - "tooltip-url": "", - "tooltip-url-label": "" + "label-url-label": "URL-címke", + "placeholder-go-to-examplecom": "Tovább az example.com webhelyre", + "placeholder-httpsexamplecomvalueraw": "https://example.com/${__value.raw}", + "placeholder-trace-id": "traceID", + "title-remove-exemplar-link": "Mintapéldány-hivatkozás eltávolítása", + "tooltip-data-source": "A mintapéldány ehhez az adatforráshoz fog navigálni.", + "tooltip-internal-link": "Engedélyezze ezt a lehetőséget, ha van belső hivatkozása. Ha engedélyezve van, megjeleníti az adatforrás-választót. Válassza ki a háttérrendszeri követési adattárat a mintapéldány-adatokhoz.", + "tooltip-label-name": "A címkeobjektum azon mezőjének neve, amelyet a traceID lekéréséhez kell használni.", + "tooltip-url": "A nyomkövetési háttérrendszer URL-címe, ahová a felhasználó menne, hogy lássa a nyomkövetést", + "tooltip-url-label": "Használja a gombcímke felülbírálásához a mintapéldány traceID mezőjén." }, "exemplars-settings": { "add": "Hozzáadás", - "no-exemplars-configurations": "", - "title-exemplars": "" + "no-exemplars-configurations": "Nincsenek mintapéldány-konfigurációk", + "title-exemplars": "Mintapéldányok" }, "prom-settings": { - "aria-label-default-editor": "", - "aria-label-prom-type-type": "", - "aria-label-prometheus-type": "", - "aria-label-select-http-method": "", + "aria-label-default-editor": "Alapértelmezett szerkesztő (kód vagy építő)", + "aria-label-prom-type-type": "{{promType}} típusa", + "aria-label-prometheus-type": "Prometheus-típus", + "aria-label-select-http-method": "HTTP-módszer kiválasztása", "editor-options": { "label-builder": "", "label-code": "" }, - "label-cache-level": "", - "label-custom-query-parameters": "", - "label-default-editor": "", - "label-disable-metrics-lookup": "", - "label-disable-recording-rules-beta": "", - "label-http-method": "", - "label-incremental-querying-beta": "", - "label-metric-names-suggestion-limit": "", - "label-prom-type-version": "", - "label-prometheus-type": "", - "label-query-overlap-window": "", - "label-query-timeout": "", - "label-scrape-interval": "", - "label-series-limit": "", - "label-use-series-endpoint": "", - "more-info": "", - "placeholder-example-maxsourceresolutionmtimeout": "", - "title-interval-behaviour": "", - "title-other": "", + "label-cache-level": "Gyorsítótár szintje", + "label-custom-query-parameters": "Egyéni lekérdezési paraméterek", + "label-default-editor": "Alapértelmezett szerkesztő", + "label-disable-metrics-lookup": "Metrikák keresésének letiltása", + "label-disable-recording-rules-beta": "Felvételkészítési szabályok letiltása (béta)", + "label-http-method": "HTTP-metódus", + "label-incremental-querying-beta": "Növekményes lekérdezés (béta)", + "label-metric-names-suggestion-limit": "Metrikanevek javaslatának korlátja", + "label-prom-type-version": "{{promType}} verziója", + "label-prometheus-type": "Prometheus-típus", + "label-query-overlap-window": "Lekérdezésátfedési ablak", + "label-query-timeout": "Lekérdezési időtúllépés", + "label-scrape-interval": "Adatgyűjtési intervallum", + "label-series-limit": "Sorozatkorlát", + "label-use-series-endpoint": "Sorozat végpontjának használata", + "more-info": "A Prometheus-típus és -verzió adatforrásokban történő konfigurálásával kapcsolatos további információkért tekintse meg a <2>kiépítési dokumentációt.", + "placeholder-example-maxsourceresolutionmtimeout": "Példa: {{example}}", + "title-interval-behaviour": "Intervallum viselkedése", + "title-other": "Egyéb", "title-performance": "Teljesítmény", - "title-query-editor": "", - "tooltip-cache-level": "", - "tooltip-custom-query-parameters": "", - "tooltip-default-editor": "", - "tooltip-disable-metrics-lookup": "", - "tooltip-disable-recording-rules-beta": "", - "tooltip-http-method": "", - "tooltip-incremental-querying-beta": "", - "tooltip-metric-names-suggestion-limit": "", - "tooltip-prom-type-version": "", - "tooltip-prometheus-type": "", - "tooltip-query-overlap-window": "", - "tooltip-query-timeout": "", - "tooltip-scrape-interval": "", - "tooltip-series-limit": "", - "tooltip-use-series-endpoint": "" + "title-query-editor": "Lekérdezésszerkesztő", + "tooltip-cache-level": "Beállítja a böngésző gyorsítótárának szintjét a szerkesztői lekérdezésekhez. A magasabb számossági adatforrásokhoz magasabb gyorsítótár-beállítások ajánlottak.", + "tooltip-custom-query-parameters": "Egyéni paraméterek hozzáadása a Prometheus lekérdezési URL-jéhez. Például: {{example1}}, {{example2}}, {{example3}} vagy {{example4}}. Több paramétert a következővel kell összefűzni: {{concatenationChar}}.", + "tooltip-default-editor": "Beállítja az alapértelmezett szerkesztőopciót az adatforrás minden felhasználója számára.", + "tooltip-disable-metrics-lookup": "Ennek az opciónak a bejelölése letiltja a metrikaválasztót és a metrika-/címketámogatást a lekérdezési mező automatikus kitöltésében. Ez segít, ha a nagyobb Prometheus-példányokkal teljesítményproblémái vannak. ", + "tooltip-disable-recording-rules-beta": "Ez a funkció letiltja a felvételkészítési szabályokat. Kapcsolja be az irányítópult teljesítményének javítása érdekében", + "tooltip-http-method": "A Prometheus-adatforrás lekérdezéséhez használhatja a POST- vagy a GET HTTP-metódust. A POST az ajánlott metódus, mivel nagyobb lekérdezéseket tesz lehetővé. Módosítsa ezt GET-re, ha a Prometheus-verziója régebbi, mint a 2.1-es, vagy ha a POST-kérések korlátozva vannak a hálózatában.", + "tooltip-incremental-querying-beta": "Ez a funkció megváltoztatja a relatív lekérdezések azon alapértelmezett viselkedését, hogy mindig friss adatokat kérjenek le a Prometheus-példányból, ehelyett a lekérdezési eredményeket a rendszer gyorsítótárazza, és csak új rekordok lesznek lekérve. Kapcsolja be az adatbázis és a hálózat terhelésének csökkentése érdekében.", + "tooltip-metric-names-suggestion-limit": "Azon metrikanevek maximális száma, amelyek automatikus kitöltési javaslatként jelenhetnek meg a lekérdezésszerkesztő Kód módjában.", + "tooltip-prom-type-version": "Ezzel állíthatja be a(z) {{promType}}-példány verzióját, ha nincs automatikusan konfigurálva.", + "tooltip-prometheus-type": "Állítsa be a Prometheus-adatbázis típusára, pl.: Prometheus, Cortex, Mimir vagy Thanos. A mező módosításával menti az aktuális beállításokat. A Prometheus bizonyos típusai támogatják vagy nem támogatják a különböző API-kat. Például egyes típusok támogatják a reguláris kifejezéses egyeztetést a címkelekérdezésekhez a teljesítmény javítása érdekében. Egyes típusok rendelkeznek API-val a metaadatokhoz. Ha ezt helytelenül állítja be, furcsa viselkedést tapasztalhat a metrikák és címkék lekérdezésekor. Ellenőrizze a Prometheus dokumentációját, hogy biztosan a megfelelő típust adja meg.", + "tooltip-query-overlap-window": "Állítson be egy időtartamot, például: {{example1}}, vagy {{example2}}, vagy {{example3}}. A(z) {{default}} alapértelmezése. Ez az időtartam hozzáadódik az egyes növekményes kérések időtartamához.", + "tooltip-query-timeout": "Állítsa be a Prometheus lekérdezési időtúllépését.", + "tooltip-scrape-interval": "Ez az intervallum azt mutatja, hogy a Prometheus milyen gyakran gyűjt adatokat a célokról. Állítsa be a Prometheus konfigurációs fájljában konfigurált tipikus adatgyűjtési és értékelési intervallumra. Ha ezt nagyobb értékre állítja, mint a Prometheus konfigurációs fájljának intervalluma, a Grafana ennek az intervallumnak megfelelően értékeli az adatokat, és kevesebb adatpontot fog látni. Alapértelmezett érték: {{default}}.", + "tooltip-series-limit": "A korlát minden erőforrásra (metrikára, címkére és értékre) vonatkozik mindkét végpont (sorozatok és címkék) esetében. Hagyja üresen a mezőt az alapértelmezett korlát (40 000) használatához. Állítsa 0-ra a korlát letiltásához és az összes lekéréséhez – ez teljesítményproblémákat okozhat. Az alapértelmezett korlát 40 000.", + "tooltip-use-series-endpoint": "Ennek az opciónak a bejelölésével előnyben részesíti a sorozat végpontját {{exampleParameter}} paraméterrel a címkeértékek végpontjához képest {{exampleParameter}} paraméterrel. Bár a címkeértékek végpontja hatékonyabbnak tekinthető, egyes felhasználók előnyben részesíthetik a sorozatot, mert rendelkezik POST-metódussal, míg a címkeértékek végpontja csak GET-metódussal rendelkezik." } }, "prom-query-legend-editor": { @@ -209,14 +209,111 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { - "content-filter-metric-names-regex-search-using": "", - "disable-text-wrap": "" + "content-filter-metric-names-regex-search-using": "A metrikanevek szűrése reguláris kifejezéses kereséssel, a Prometheus API további hívásával.", + "disable-text-wrap": "Sortörés letiltása" }, "feedback-link": { "give-feedback": "Visszajelzés küldése", - "title-give-feedback": "" + "title-give-feedback": "A metrikaböngésző új, és számítunk az építő jellegű visszajelzésére" }, "get-collapsed-info": { "exemplars": "", @@ -250,18 +347,18 @@ }, "handle-function": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "A lekérdezés elemzése nem egyértelmű." } }, "label-filter-item": { - "aria-label-remove": "", - "placeholder-select-label": "", + "aria-label-remove": "{{name}} eltávolítása", + "placeholder-select-label": "Címke kiválasztása", "placeholder-select-value": "Érték kijelölése" }, "label-filters": { - "label-filters": "", - "label-label-filters": "", - "tooltip-label-filters": "" + "label-filters": "Címkeszűrők", + "label-label-filters": "Címkeszűrők", + "tooltip-label-filters": "Opcionális: a lekérdezéstípushoz kiválasztott metrika szűrésére szolgál." }, "label-param-editor": { "loadingMessage-loading-labels": "", @@ -269,92 +366,99 @@ }, "metric-combobox": { "async-select": { - "aria-label-open-metrics-explorer": "", - "placeholder-select-metric": "", - "tooltip-open-metrics-explorer": "" + "aria-label-open-metrics-explorer": "Metrikaböngésző megnyitása", + "placeholder-select-metric": "Metrika kiválasztása", + "tooltip-open-metrics-explorer": "Metrikaböngésző megnyitása" }, "label-metric": "Metrika", - "tooltip-metric": "" + "tooltip-metric": "Opcionális: a megadott metrikában a címke nevéhez tartozó címkeértékek listáját adja vissza." }, "metrics-modal": { - "additional-settings": "", + "additional-settings": "További beállítások", "aria-label-additional-settings": "További beállítások", - "aria-label-browse-metrics": "", - "currently-selected": "", - "metrics-pre-filtered": "", - "placeholder-results-per-page": "", - "results-per-page": "", - "title-metrics-explorer": "", - "results-amount_one": "", - "results-amount_other": "" + "aria-label-browse-metrics": "Metrikák böngészése", + "currently-selected": "Jelenleg kiválasztott: {{selected}}", + "metrics-pre-filtered": "Ezek a metrikák előszűrtek a címkeszűrőkben kiválasztott címkék alapján.", + "placeholder-results-per-page": "találat oldalanként", + "results-per-page": "Találatok száma oldalanként", + "title-metrics-explorer": "Metrikaböngésző", + "results-amount_one": "{{count}}/{{num}} találat megjelenítése", + "results-amount_other": "{{count}}/{{num}} találat megjelenítése" }, "nested-query": { "label": { - "ignoring": "", + "ignoring": "Mellőzés", "on": "Be" }, "operator": "Műveleti jel", - "tooltip-remove-match": "", - "vector-matches": "" + "tooltip-remove-match": "Egyezés eltávolítása", + "vector-matches": "Vektoregyezések" }, "operation-editor": { - "not-found": "", - "title-remove": "" + "not-found": "A(z) {{id}} művelet nem található", + "title-remove": "{{name}} eltávolítása" }, "operation-header": { "placeholder-replace-with": "Csere ezzel:", - "title-click-to-view-alternative-operations": "", - "title-remove-operation": "" + "title-click-to-view-alternative-operations": "Kattintson az alternatív műveletek megtekintéséhez", + "title-remove-operation": "Művelet eltávolítása" }, "operation-info-button": { - "title-click-to-show-description": "", - "title-remove-operation": "" + "title-click-to-show-description": "Kattintson a leírás megjelenítéséhez", + "title-remove-operation": "Művelet eltávolítása" }, "operation-list": { - "operations": "", + "operations": "Műveletek", "placeholder-search": "Keresés", - "title-add-operation": "" + "title-add-operation": "Művelet hozzáadása" }, "operation-param-editor": { - "title-add": "", - "title-remove": "" + "title-add": "{{name}} hozzáadása", + "title-remove": "{{name}} eltávolítása" + }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } }, "prom-query-builder-options": { - "aria-label-lower-limit-parameter": "", - "aria-label-select-resolution": "", + "aria-label-lower-limit-parameter": "Állítsa be a léptékparaméter alsó határát", + "aria-label-select-resolution": "Felbontás kiválasztása", "format-options": { "label-heatmap": "", "label-table": "", "label-time-series": "" }, - "label-exemplars": "", + "label-exemplars": "Mintapéldányok", "label-format": "Formátum", - "label-min-step": "", - "label-resolution": "", + "label-min-step": "Min. lépték", + "label-resolution": "Felbontás", "label-type": "Típus", - "placeholder-auto": "", + "placeholder-auto": "automatikus", "title-options": "Beállítások", - "tooltip-min-step": "" + "tooltip-min-step": "További alsó határérték a Prometheus-lekérdezés léptékparaméteréhez, valamint a(z) <2>{{interval}} és <4>{{rateInterval}} változóhoz." }, "prom-query-code-editor-autocomplete-info": { - "autocomplete-suggestions-limited": "", - "tooltip-autocomplete-suggestions-limited": "" + "autocomplete-suggestions-limited": "Automatikus kitöltési javaslatok korlátozva", + "tooltip-autocomplete-suggestions-limited": "A metrikanevek száma meghaladja az automatikus kitöltés korlátját. Csak a(z) {{autocompleteLimit}} legrelevánsabb metrika jelenik meg. A küszöbértéket az adatforrás beállításai között módosíthatja." }, "prom-query-editor-selector": { "body-syntax-error": "", "confirmText-continue": "", "kick-start-your-query": "Előbeállításos lekérdezés", - "label-explain": "", - "run-queries": "", - "title-parsing-error-switch-builder": "" + "label-explain": "Magyarázat", + "run-queries": "Lekérdezések futtatása", + "title-parsing-error-switch-builder": "Elemzési hiba: Váltás építő módba?" }, "prom-query-legend-editor": { - "label-legend": "", - "placeholder-select-legend-mode": "", - "tooltip-legend": "" + "label-legend": "Jelmagyarázat", + "placeholder-select-legend-mode": "Jelmagyarázat mód kiválasztása", + "tooltip-legend": "Sorozatnév felülbírálása vagy sablon. Például a(z) {{templateExample}} helyébe a(z) {{labelName}} címkeértéke lép." }, "query-builder-hints": { - "hint-details": "" + "hint-details": "tipp: {{hintDetails}}" }, "query-editor-mode-toggle": { "editor-modes": { @@ -363,30 +467,30 @@ } }, "query-pattern": { - "apply-query": "", - "aria-label-apply-query-starter-button": "", - "aria-label-back-button": "", - "aria-label-create-new-query-button": "", - "aria-label-raw-query": "", - "aria-label-use-this-query-button": "", + "apply-query": "Lekérdezés alkalmazása", + "aria-label-apply-query-starter-button": "lekérdezés-előbeállító alkalmazása gomb", + "aria-label-back-button": "vissza gomb", + "aria-label-create-new-query-button": "új lekérdezés létrehozása gomb", + "aria-label-raw-query": "{{patternName}} nyers lekérdezés", + "aria-label-use-this-query-button": "használja ezt a lekérdezést gomb", "back": "Vissza", - "create-new-query": "", + "create-new-query": "Új lekérdezés létrehozása", "use-this-query": "Használja ezt a lekérdezést" }, "query-patterns-modal": { - "aria-label-close-kick-start-your-query-modal": "", + "aria-label-close-kick-start-your-query-modal": "előbeállításos lekérdezés modális ablakának bezárása", "aria-label-kick-start-your-query-modal": "Előbeállításos lekérdezés modális ablak", - "aria-label-toggle-query-starter": "", + "aria-label-toggle-query-starter": "{{patternType}} lekérdezés-előbeállító kártya megnyitása és bezárása", "close": "Bezárás", - "description-kick-start-your-query": "", - "label-toggle-query-starter": "", + "description-kick-start-your-query": "Kezdje el létrehozni a lekérdezést előbeállításokkal az alábbi lekérdezések egyikének kiválasztásával. Ezután folytathatja a lekérdezés befejezését.", + "label-toggle-query-starter": "{{patternType}} lekérdezés-előbeállítók", "title-kick-start-your-query": "Előbeállításos lekérdezés" }, "raw-query": { - "aria-label-selector": "" + "aria-label-selector": "választó" }, "results-table": { - "content-descriptive-type": "", + "content-descriptive-type": "{{descriptiveType}} létrehozásakor a Prometheus több, számláló típusú sorozatot jelenít meg. ", "description": "Leírás", "message-expand-label-filters": "", "message-expand-search": "", @@ -397,7 +501,7 @@ }, "update-function-args": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "A lekérdezés elemzése nem egyértelmű." } } } diff --git a/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json index 51f85b929bb..f80f5a3d833 100644 --- a/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json @@ -2,201 +2,201 @@ "grafana-prometheus": { "components": { "annotation-query-editor": { - "annotation-data-load-error": "", - "aria-label-lower-limit-parameter": "", - "label-min-step": "", - "label-series-value-as-timestamp": "", + "annotation-data-load-error": "Terjadi kesalahan pemuatan data anotasi!", + "aria-label-lower-limit-parameter": "Tetapkan batas bawah untuk parameter langkah", + "label-min-step": "Langkah minimum", + "label-series-value-as-timestamp": "Nilai data seri sebagai stempel waktu", "label-tags": "Tag", - "label-text": "", + "label-text": "Teks", "label-title": "Judul", - "placeholder-auto": "", - "tooltip-either-pattern-example-instance-replaced-label": "", - "tooltip-min-step": "", - "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "" + "placeholder-auto": "auto", + "tooltip-either-pattern-example-instance-replaced-label": "Gunakan nama atau pola. Misalnya, {{labelTemplate}} diganti dengan nilai label untuk label {{labelName}}.", + "tooltip-min-step": "Batas bawah tambahan untuk parameter langkah kueri Prometheus dan untuk variabel <2>{{intervalVar}} dan <4>{{rateIntervalVar}}.", + "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "Unit stempel waktu adalah milidetik. Jika satuan nilai data seri adalah detik, kalikan vektor rentangnya dengan 1000." }, "get-query-type-options": { "description": { - "instant-query-range": "" + "instant-query-range": "Jalankan kueri Instan dan kueri Rentang" }, "label": { - "both": "" + "both": "Keduanya" }, "range-options": { "description": { - "query-range": "" + "query-range": "Jalankan kueri selama rentang waktu tertentu" }, "label": { - "instant": "", - "range": "" + "instant": "Instan", + "range": "Rentang" } } }, "label-selector": { - "aria-label-filter-expression-for-label": "", - "description-select-labels": "", - "select-labels-to-search-in": "" + "aria-label-filter-expression-for-label": "Filter persamaan berdasarkan label", + "description-select-labels": "Setelah nilai label dipilih, hanya kombinasi label yang mungkin yang ditampilkan.", + "select-labels-to-search-in": "2. Pilih label untuk pencarian" }, "metric-selector": { - "aria-label-filter-expression-for-metric": "", - "aria-label-limit-results-from-series-endpoint": "", - "description-series-limit": "", - "label-select-metric": "", - "select-a-metric": "", - "series-limit": "" + "aria-label-filter-expression-for-metric": "Filter persamaan berdasarkan metrik", + "aria-label-limit-results-from-series-endpoint": "Batasi hasil dari titik akhir data seri", + "description-series-limit": "Batas ini berlaku untuk semua metrik, label, dan nilai. Biarkan bidang kosong untuk menggunakan batas default. Atur ke 0 untuk menonaktifkan batas dan mengambil semuanya — ini dapat menyebabkan masalah kinerja.", + "label-select-metric": "Setelah metrik dipilih, hanya label yang mungkin yang ditampilkan. Label dibatasi oleh batas data seri di bawah ini.", + "select-a-metric": "1. Pilih metrik", + "series-limit": "Batas data seri" }, "prom-cheat-sheet": { - "prom-ql-cheat-sheet": "" + "prom-ql-cheat-sheet": "Lembar Ringkasan PromQL" }, "prom-exemplar-field": { - "exemplars": "", - "tooltip-disable-query": "", - "tooltip-enable-query": "" + "exemplars": "Contoh", + "tooltip-disable-query": "Nonaktifkan kueri dengan contoh", + "tooltip-enable-query": "Aktifkan kueri dengan contoh" }, "prom-explore-extra-field": { - "aria-label-prometheus-extra-field": "", - "aria-label-query-type-field": "", - "aria-label-step-field": "", - "min-step": "", - "query-type": "", - "tooltip-units-builtin-variables-example-interval-rateinterval": "" + "aria-label-prometheus-extra-field": "Bidang tambahan Prometheus", + "aria-label-query-type-field": "Bidang jenis kueri", + "aria-label-step-field": "Bidang langkah", + "min-step": "Langkah minimum", + "query-type": "Jenis kueri", + "tooltip-units-builtin-variables-example-interval-rateinterval": "Unit waktu dan variabel bawaan dapat digunakan di sini, misalnya: {{example1}}, {{example2}}, {{example3}}, {{example4}}, {{example5}}, {{example6}}, {{example7}} (Default jika tidak ada unit yang ditentukan: {{default}})" }, "prom-query-field": { - "placeholder-enter-a-prom-ql-query": "" + "placeholder-enter-a-prom-ql-query": "Masukkan kueri PromQL…" }, "prom-variable-query-editor": { - "aria-label-classic-query": "", - "aria-label-metric-regex": "", - "aria-label-metric-selector": "", - "aria-label-prometheus-query": "", - "aria-label-query-type": "", - "aria-label-series-query": "", - "label-classic-query": "", + "aria-label-classic-query": "Kueri Klasik", + "aria-label-metric-regex": "Ekspresi reguler metrik", + "aria-label-metric-selector": "Selektor metrik", + "aria-label-prometheus-query": "Kueri Prometheus", + "aria-label-query-type": "Jenis kueri", + "aria-label-series-query": "Kueri Data Seri", + "label-classic-query": "Kueri Klasik", "label-label": "Label", - "label-metric-regex": "", + "label-metric-regex": "Ekspresi reguler metrik", "label-query": "Kueri", - "label-query-type": "", - "label-series-query": "", - "placeholder-classic-query": "", - "placeholder-metric-regex": "", - "placeholder-prometheus-query": "", + "label-query-type": "Jenis kueri", + "label-series-query": "Kueri Data Seri", + "placeholder-classic-query": "Kueri Klasik", + "placeholder-metric-regex": "Ekspresi reguler metrik", + "placeholder-prometheus-query": "Kueri Prometheus", "placeholder-select-query-type": "Pilih jenis kueri", - "placeholder-series-query": "", - "returns-metrics-matching-specified-metric-regex": "", - "tooltip-classic-query": "", - "tooltip-label": "", - "tooltip-metric-regex": "", - "tooltip-query": "", - "tooltip-query-type": "", - "tooltip-series-query": "" + "placeholder-series-query": "Kueri Data Seri", + "returns-metrics-matching-specified-metric-regex": "Mengembalikan daftar metrik yang sesuai dengan regex metrik yang ditentukan.", + "tooltip-classic-query": "Implementasi awal dari editor kueri variabel Prometheus. Masukkan string dengan jenis kueri dan parameter yang benar seperti yang dijelaskan dalam dokumen ini. Misalnya, {{exampleQuery}}.", + "tooltip-label": "Mengembalikan daftar nilai label untuk nama label di semua metrik kecuali metrik tersebut ditentukan.", + "tooltip-metric-regex": "Mengembalikan daftar nama label, secara opsional menyaring berdasarkan regex metrik yang ditentukan.", + "tooltip-query": "Mengembalikan daftar hasil kueri Prometheus untuk kueri tersebut. Ini dapat mencakup fungsi Prometheus, yaitu {{exampleQuery}}.", + "tooltip-query-type": "Plugin sumber data Prometheus menyediakan jenis kueri berikut untuk variabel templat.", + "tooltip-series-query": "Masukkan metrik dengan label, hanya metrik atau hanya label, yaitu {{example1}}, {{example2}}, atau {{example3}}. Mengembalikan daftar deret waktu yang terkait dengan data yang dimasukkan." }, "selector-actions": { - "aria-label-selector": "", - "aria-label-selector-clear-button": "", - "aria-label-use-selector-as-metrics-button": "", - "aria-label-use-selector-for-query-button": "", - "aria-label-validate-submit-button": "", + "aria-label-selector": "selektor", + "aria-label-selector-clear-button": "Tombol hapus selektor", + "aria-label-use-selector-as-metrics-button": "Gunakan selektor sebagai tombol metrik", + "aria-label-use-selector-for-query-button": "Gunakan selektor untuk tombol kueri", + "aria-label-validate-submit-button": "Validasi tombol kirim", "clear": "Hapus", - "resulting-selector": "", - "use-as-rate-query": "", - "use-query": "", - "validate-selector": "" + "resulting-selector": "4. Selektor yang dihasilkan", + "use-as-rate-query": "Gunakan sebagai kueri rate", + "use-query": "Gunakan kueri", + "validate-selector": "Validasi selektor" }, "value-selector": { - "aria-label-filter-expression-for-label-values": "", - "aria-label-values-for": "", - "description-search-field-values-across-selected-labels": "", - "select-multiple-values-for-your-labels": "" + "aria-label-filter-expression-for-label-values": "Filter persamaan berdasarkan nilai label", + "aria-label-values-for": "Nilai untuk {{labelKey}}", + "description-search-field-values-across-selected-labels": "Gunakan bidang pencarian untuk menemukan nilai di seluruh label yang dipilih.", + "select-multiple-values-for-your-labels": "3. Pilih (beberapa) nilai untuk label Anda" } }, "configuration": { "alerting-settings-overhaul": { - "label-allow-as-recording-rules-target": "", - "label-manage-alerts-via-alerting-ui": "", + "label-allow-as-recording-rules-target": "Izinkan sebagai target aturan perekaman", + "label-manage-alerts-via-alerting-ui": "Kelola peringatan melalui UI Peringatan", "title-alerting": "Alerting", - "tooltip-allow-as-recording-rules-target": "", + "tooltip-allow-as-recording-rules-target": "Izinkan sumber data ini dipilih sebagai target untuk menulis aturan perekaman.", "tooltip-manage-alerts-via-alerting-ui": "Kelola aturan peringatan untuk sumber data ini. Untuk mengelola sumber daya alerting lainnya, tambahkan sumber data Alertmanager." }, "config-editor": { - "browser-access-mode-error": "", - "description-advanced-settings": "", + "browser-access-mode-error": "Mode akses browser di sumber data Prometheus tidak lagi tersedia. Beralih ke mode akses server.", + "description-advanced-settings": "Pengaturan tambahan adalah pengaturan opsional yang dapat dikonfigurasi agar memiliki kendali lebih besar atas sumber data Anda.", "title-advanced-settings": "Pengaturan lanjutan", "title-error": "Kesalahan" }, "data-source-http-settings-overhaul": { "tooltip-browser-access-mode": "Metode akses Anda adalah <1>Browser, ini berarti URL harus dapat diakses dari browser.", - "tooltip-http-url": "", + "tooltip-http-url": "Tentukan URL HTTP lengkap (misalnya {{exampleURL}})", "tooltip-server-access-mode": "Metode akses Anda adalah <1>Server, ini berarti URL harus dapat diakses dari backend/server grafana." }, "docs-tip": { - "visit-docs-for-more-details-here": "" + "visit-docs-for-more-details-here": "Kunjungi dokumen untuk detail selengkapnya di sini." }, "exemplar-setting": { "label-data-source": "Sumber data", - "label-internal-link": "", + "label-internal-link": "Tautan internal", "label-label-name": "Nama label", - "label-remove-exemplar-link": "", + "label-remove-exemplar-link": "Hapus tautan contoh", "label-url": "URL", - "label-url-label": "", - "placeholder-go-to-examplecom": "", - "placeholder-httpsexamplecomvalueraw": "", - "placeholder-trace-id": "", - "title-remove-exemplar-link": "", - "tooltip-data-source": "", - "tooltip-internal-link": "", - "tooltip-label-name": "", - "tooltip-url": "", - "tooltip-url-label": "" + "label-url-label": "Label URL", + "placeholder-go-to-examplecom": "Buka example.com", + "placeholder-httpsexamplecomvalueraw": "https://example.com/${__value.raw}", + "placeholder-trace-id": "traceID", + "title-remove-exemplar-link": "Hapus tautan contoh", + "tooltip-data-source": "Sumber data yang akan dinavigasi oleh contoh.", + "tooltip-internal-link": "Aktifkan opsi ini jika Anda memiliki tautan internal. Saat diaktifkan, ini akan menampilkan selektor sumber data. Pilih penyimpanan data pelacakan backend untuk data contoh Anda.", + "tooltip-label-name": "Nama bidang dalam objek label yang harus digunakan untuk mendapatkan traceID.", + "tooltip-url": "URL backend jejak yang akan digunakan pengguna untuk melihat jejaknya", + "tooltip-url-label": "Gunakan untuk mengganti label tombol pada bidang traceID contoh." }, "exemplars-settings": { "add": "Tambahkan", - "no-exemplars-configurations": "", - "title-exemplars": "" + "no-exemplars-configurations": "Tidak ada konfigurasi contoh", + "title-exemplars": "Contoh" }, "prom-settings": { - "aria-label-default-editor": "", - "aria-label-prom-type-type": "", - "aria-label-prometheus-type": "", - "aria-label-select-http-method": "", + "aria-label-default-editor": "Editor Default (Kode atau Pembuat)", + "aria-label-prom-type-type": "Jenis {{promType}}", + "aria-label-prometheus-type": "Jenis Prometheus", + "aria-label-select-http-method": "Pilih metode HTTP", "editor-options": { "label-builder": "", "label-code": "" }, - "label-cache-level": "", - "label-custom-query-parameters": "", - "label-default-editor": "", - "label-disable-metrics-lookup": "", - "label-disable-recording-rules-beta": "", - "label-http-method": "", - "label-incremental-querying-beta": "", - "label-metric-names-suggestion-limit": "", - "label-prom-type-version": "", - "label-prometheus-type": "", - "label-query-overlap-window": "", - "label-query-timeout": "", - "label-scrape-interval": "", - "label-series-limit": "", - "label-use-series-endpoint": "", - "more-info": "", - "placeholder-example-maxsourceresolutionmtimeout": "", - "title-interval-behaviour": "", - "title-other": "", + "label-cache-level": "Level cache", + "label-custom-query-parameters": "Parameter kueri kustom", + "label-default-editor": "Editor default", + "label-disable-metrics-lookup": "Nonaktifkan pencarian metrik", + "label-disable-recording-rules-beta": "Nonaktifkan aturan perekaman (beta)", + "label-http-method": "Metode HTTP", + "label-incremental-querying-beta": "Kueri inkremental (beta)", + "label-metric-names-suggestion-limit": "Batas saran nama metrik", + "label-prom-type-version": "Versi {{promType}}", + "label-prometheus-type": "Jenis Prometheus", + "label-query-overlap-window": "Jendela kueri tumpang tindih", + "label-query-timeout": "Batas waktu kueri habis", + "label-scrape-interval": "Interval scrape", + "label-series-limit": "Batas data seri", + "label-use-series-endpoint": "Gunakan titik akhir data seri", + "more-info": "Untuk informasi lengkap tentang mengonfigurasi jenis dan versi prometheus dalam sumber data, lihat <2>dokumentasi penyediaan.", + "placeholder-example-maxsourceresolutionmtimeout": "Contoh: {{example}}", + "title-interval-behaviour": "Perilaku interval", + "title-other": "Lainnya", "title-performance": "Kinerja", - "title-query-editor": "", - "tooltip-cache-level": "", - "tooltip-custom-query-parameters": "", - "tooltip-default-editor": "", - "tooltip-disable-metrics-lookup": "", - "tooltip-disable-recording-rules-beta": "", - "tooltip-http-method": "", - "tooltip-incremental-querying-beta": "", - "tooltip-metric-names-suggestion-limit": "", - "tooltip-prom-type-version": "", - "tooltip-prometheus-type": "", - "tooltip-query-overlap-window": "", - "tooltip-query-timeout": "", - "tooltip-scrape-interval": "", - "tooltip-series-limit": "", - "tooltip-use-series-endpoint": "" + "title-query-editor": "Editor kueri", + "tooltip-cache-level": "Mengatur level cache peramban untuk kueri editor. Pengaturan cache yang lebih tinggi disarankan untuk sumber data dengan kardinalitas tinggi.", + "tooltip-custom-query-parameters": "Tambahkan parameter kustom ke URL kueri Prometheus. Misalnya {{example1}}, {{example2}}, {{example3}}, atau {{example4}}. Beberapa parameter harus digabungkan bersama dengan {{concatenationChar}}.", + "tooltip-default-editor": "Atur opsi editor default untuk semua pengguna sumber data ini.", + "tooltip-disable-metrics-lookup": "Memeriksa opsi ini akan menonaktifkan pemilih metrik dan dukungan metrik/label dalam pelengkapan otomatis bidang kueri. Ini membantu jika Anda memiliki masalah kinerja dengan instans Prometheus yang lebih besar. ", + "tooltip-disable-recording-rules-beta": "Fitur ini akan menonaktifkan aturan perekaman. Aktifkan ini untuk meningkatkan kinerja dasbor", + "tooltip-http-method": "Anda dapat menggunakan metode HTTP POST atau GET untuk melakukan kueri sumber data Prometheus Anda. POST adalah metode yang disarankan karena memungkinkan kueri yang lebih besar. Ubah ini menjadi GET jika Anda memiliki versi Prometheus yang lebih lama dari 2.1 atau jika permintaan POST dibatasi di jaringan Anda.", + "tooltip-incremental-querying-beta": "Fitur ini akan mengubah perilaku default kueri relatif: alih-alih selalu meminta data terbaru dari instans Prometheus, hasil kueri akan disimpan sementara, dan hanya data baru yang akan diminta. Aktifkan ini untuk mengurangi beban basis data dan jaringan.", + "tooltip-metric-names-suggestion-limit": "Jumlah maksimum nama metrik yang mungkin muncul sebagai saran pelengkapan otomatis dalam mode Kode editor kueri.", + "tooltip-prom-type-version": "Gunakan ini untuk mengatur versi instans {{promType}} Anda jika tidak dikonfigurasi secara otomatis.", + "tooltip-prometheus-type": "Atur ini ke jenis basis data prometheus Anda, misalnya Prometheus, Cortex, Mimir, atau Thanos. Mengubah bidang ini akan menyimpan pengaturan Anda saat ini. Jenis Prometheus tertentu mendukung atau tidak mendukung berbagai API. Misalnya, beberapa jenis mendukung pencocokan regex bagi kueri label untuk meningkatkan kinerja. Beberapa jenis memiliki API untuk metadata. Jika Anda mengatur ini secara tidak benar, Anda mungkin mengalami perilaku tidak biasa saat melakukan kueri metrik dan label. Periksa dokumentasi Prometheus Anda untuk memastikan Anda memasukkan jenis yang benar.", + "tooltip-query-overlap-window": "Tetapkan durasi seperti {{example1}} atau {{example2}} atau {{example3}}. Default {{default}}. Durasi ini akan ditambahkan ke durasi setiap permintaan tambahan.", + "tooltip-query-timeout": "Atur batas waktu kueri Prometheus.", + "tooltip-scrape-interval": "Interval ini adalah seberapa sering Prometheus melakukan scrape pada target. Atur ini ke interval scrape dan evaluasi umum yang dikonfigurasi dalam file konfigurasi Prometheus Anda. Jika Anda mengatur ini ke nilai yang lebih besar dari interval file konfigurasi Prometheus Anda, Grafana akan mengevaluasi data sesuai dengan interval ini dan Anda akan melihat lebih sedikit titik data. Default ke {{default}}.", + "tooltip-series-limit": "Batas ini berlaku untuk semua sumber daya (metrik, label, dan nilai) untuk kedua endpoint (deret dan label). Biarkan bidang kosong untuk menggunakan batas default (40000). Atur ke 0 untuk menonaktifkan batas dan mengambil semuanya — ini dapat menyebabkan masalah kinerja. Batas default adalah 40000.", + "tooltip-use-series-endpoint": "Memeriksa opsi ini akan mengutamakan titik akhir data seri dengan {{exampleParameter}} parameter daripada titik akhir nilai label dengan {{exampleParameter}} parameter. Meskipun titik akhir nilai label dianggap memiliki kinerja yang lebih baik, beberapa pengguna mungkin lebih menyukai data seri karena memiliki metode POST, sementara titik akhir nilai label hanya memiliki metode GET." } }, "prom-query-legend-editor": { @@ -209,14 +209,111 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { - "content-filter-metric-names-regex-search-using": "", - "disable-text-wrap": "" + "content-filter-metric-names-regex-search-using": "Filter nama metrik dengan pencarian regex, menggunakan panggilan tambahan pada API Prometheus.", + "disable-text-wrap": "Nonaktifkan pemenggalan teks otomatis" }, "feedback-link": { "give-feedback": "Berikan umpan balik", - "title-give-feedback": "" + "title-give-feedback": "Penjelajah metrik ini masih baru, beri tahu kami cara untuk meningkatkannya" }, "get-collapsed-info": { "exemplars": "", @@ -250,18 +347,18 @@ }, "handle-function": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "Penguraian kueri mengalami masalah ketidakjelasan." } }, "label-filter-item": { - "aria-label-remove": "", - "placeholder-select-label": "", + "aria-label-remove": "Hapus {{name}}", + "placeholder-select-label": "Pilih label", "placeholder-select-value": "Pilih nilai" }, "label-filters": { - "label-filters": "", - "label-label-filters": "", - "tooltip-label-filters": "" + "label-filters": "Filter label", + "label-label-filters": "Filter label", + "tooltip-label-filters": "Opsional: digunakan untuk menyaring metrik yang dipilih untuk jenis kueri ini." }, "label-param-editor": { "loadingMessage-loading-labels": "", @@ -269,91 +366,98 @@ }, "metric-combobox": { "async-select": { - "aria-label-open-metrics-explorer": "", - "placeholder-select-metric": "", - "tooltip-open-metrics-explorer": "" + "aria-label-open-metrics-explorer": "Buka penjelajah metrik", + "placeholder-select-metric": "Pilih metrik", + "tooltip-open-metrics-explorer": "Buka penjelajah metrik" }, "label-metric": "Metrik", - "tooltip-metric": "" + "tooltip-metric": "Opsional: mengembalikan daftar nilai label untuk nama label dalam metrik yang ditentukan." }, "metrics-modal": { - "additional-settings": "", + "additional-settings": "Pengaturan Tambahan", "aria-label-additional-settings": "Pengaturan tambahan", - "aria-label-browse-metrics": "", - "currently-selected": "", - "metrics-pre-filtered": "", - "placeholder-results-per-page": "", - "results-per-page": "", - "title-metrics-explorer": "", - "results-amount_other": "" + "aria-label-browse-metrics": "Telusuri metrik", + "currently-selected": "Sedang dipilih: {{selected}}", + "metrics-pre-filtered": "Metrik ini telah difilter sebelumnya berdasarkan label yang dipilih di filter label.", + "placeholder-results-per-page": "hasil per halaman", + "results-per-page": "Hasil per halaman", + "title-metrics-explorer": "Penjelajah metrik", + "results-amount_other": "Menampilkan {{num}} dari {{count}} hasil" }, "nested-query": { "label": { - "ignoring": "", + "ignoring": "Mengabaikan", "on": "Aktif" }, "operator": "Operator", - "tooltip-remove-match": "", - "vector-matches": "" + "tooltip-remove-match": "Hapus kecocokan", + "vector-matches": "Kecocokan vektor" }, "operation-editor": { - "not-found": "", - "title-remove": "" + "not-found": "Operasi {{id}} tidak ditemukan", + "title-remove": "Hapus {{name}}" }, "operation-header": { "placeholder-replace-with": "Ganti dengan", - "title-click-to-view-alternative-operations": "", - "title-remove-operation": "" + "title-click-to-view-alternative-operations": "Klik untuk melihat operasi alternatif", + "title-remove-operation": "Hapus operasi" }, "operation-info-button": { - "title-click-to-show-description": "", - "title-remove-operation": "" + "title-click-to-show-description": "Klik untuk menampilkan deskripsi", + "title-remove-operation": "Hapus operasi" }, "operation-list": { - "operations": "", + "operations": "Operasi", "placeholder-search": "Cari", - "title-add-operation": "" + "title-add-operation": "Tambah operasi" }, "operation-param-editor": { - "title-add": "", - "title-remove": "" + "title-add": "Tambahkan {{name}}", + "title-remove": "Hapus {{name}}" + }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } }, "prom-query-builder-options": { - "aria-label-lower-limit-parameter": "", - "aria-label-select-resolution": "", + "aria-label-lower-limit-parameter": "Tetapkan batas bawah untuk parameter langkah", + "aria-label-select-resolution": "Pilih resolusi", "format-options": { "label-heatmap": "", "label-table": "", "label-time-series": "" }, - "label-exemplars": "", + "label-exemplars": "Contoh", "label-format": "Format", - "label-min-step": "", - "label-resolution": "", + "label-min-step": "Langkah minimum", + "label-resolution": "Resolusi", "label-type": "Jenis", - "placeholder-auto": "", + "placeholder-auto": "auto", "title-options": "Opsi", - "tooltip-min-step": "" + "tooltip-min-step": "Batas bawah tambahan untuk parameter langkah kueri Prometheus dan untuk variabel <2>{{interval}} dan <4>{{rateInterval}}." }, "prom-query-code-editor-autocomplete-info": { - "autocomplete-suggestions-limited": "", - "tooltip-autocomplete-suggestions-limited": "" + "autocomplete-suggestions-limited": "Saran pelengkapan otomatis dibatasi", + "tooltip-autocomplete-suggestions-limited": "Jumlah nama metrik melebihi batas pelengkapan otomatis. Hanya {{autocompleteLimit}} metrik yang paling relevan yang ditampilkan. Anda dapat menyesuaikan ambang batas dalam pengaturan sumber data." }, "prom-query-editor-selector": { "body-syntax-error": "", "confirmText-continue": "", "kick-start-your-query": "Mulai kueri Anda", - "label-explain": "", - "run-queries": "", - "title-parsing-error-switch-builder": "" + "label-explain": "Penjelasan", + "run-queries": "Jalankan kueri", + "title-parsing-error-switch-builder": "Terjadi kesalahan penguraian: Beralih ke mode pembuat?" }, "prom-query-legend-editor": { - "label-legend": "", - "placeholder-select-legend-mode": "", - "tooltip-legend": "" + "label-legend": "Legenda", + "placeholder-select-legend-mode": "Pilih mode legenda", + "tooltip-legend": "Mengganti nama seri atau templat. Misalnya, {{templateExample}} akan diganti dengan nilai label untuk {{labelName}}." }, "query-builder-hints": { - "hint-details": "" + "hint-details": "petunjuk: {{hintDetails}}" }, "query-editor-mode-toggle": { "editor-modes": { @@ -362,30 +466,30 @@ } }, "query-pattern": { - "apply-query": "", - "aria-label-apply-query-starter-button": "", - "aria-label-back-button": "", - "aria-label-create-new-query-button": "", - "aria-label-raw-query": "", - "aria-label-use-this-query-button": "", + "apply-query": "Terapkan kueri", + "aria-label-apply-query-starter-button": "tombol mulai terapkan kueri", + "aria-label-back-button": "tombol kembali", + "aria-label-create-new-query-button": "tombol buat kueri baru", + "aria-label-raw-query": "Kueri {{patternName}} mentah", + "aria-label-use-this-query-button": "tombol gunakan kueri ini", "back": "Kembali", - "create-new-query": "", + "create-new-query": "Buat kueri baru", "use-this-query": "Gunakan kueri ini" }, "query-patterns-modal": { - "aria-label-close-kick-start-your-query-modal": "", + "aria-label-close-kick-start-your-query-modal": "tutup modal untuk memulai kueri Anda", "aria-label-kick-start-your-query-modal": "Mulai modal kueri Anda", - "aria-label-toggle-query-starter": "", + "aria-label-toggle-query-starter": "buka dan tutup kartu pemula kueri {{patternType}}", "close": "Tutup", - "description-kick-start-your-query": "", - "label-toggle-query-starter": "", + "description-kick-start-your-query": "Mulai kueri Anda dengan memilih salah satu kueri ini. Anda kemudian dapat melanjutkan untuk menyelesaikan kueri Anda.", + "label-toggle-query-starter": "kueri awal {{patternType}}", "title-kick-start-your-query": "Mulai kueri Anda" }, "raw-query": { - "aria-label-selector": "" + "aria-label-selector": "selektor" }, "results-table": { - "content-descriptive-type": "", + "content-descriptive-type": "Saat membuat {{descriptiveType}}, Prometheus mengekspos beberapa data seri dengan penghitung jenis. ", "description": "Deskripsi", "message-expand-label-filters": "", "message-expand-search": "", @@ -396,7 +500,7 @@ }, "update-function-args": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "Penguraian kueri mengalami masalah ketidakjelasan." } } } diff --git a/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json index 44db96a498b..443fd697bad 100644 --- a/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json index 13c91887eab..962ded0d7be 100644 --- a/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json @@ -2,201 +2,201 @@ "grafana-prometheus": { "components": { "annotation-query-editor": { - "annotation-data-load-error": "", - "aria-label-lower-limit-parameter": "", - "label-min-step": "", - "label-series-value-as-timestamp": "", + "annotation-data-load-error": "注釈データの読み込みエラー!", + "aria-label-lower-limit-parameter": "ステップパラメーターの下限を設定", + "label-min-step": "最小ステップ", + "label-series-value-as-timestamp": "タイムスタンプとしての系列値", "label-tags": "タグ", - "label-text": "", + "label-text": "テキスト", "label-title": "タイトル", - "placeholder-auto": "", - "tooltip-either-pattern-example-instance-replaced-label": "", - "tooltip-min-step": "", - "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "" + "placeholder-auto": "自動", + "tooltip-either-pattern-example-instance-replaced-label": "名前またはパターンのいずれかを使用します。例えば、{{labelTemplate}}はラベル{{labelName}}のラベル値に置き換えられます。", + "tooltip-min-step": "Prometheusクエリのステップパラメーターと<2>{{intervalVar}}および<4>{{rateIntervalVar}}変数の追加下限。", + "tooltip-timestamp-milliseconds-series-value-seconds-multiply": "タイムスタンプの単位はミリ秒です。系列値の単位が秒の場合、その範囲ベクトルに1000を掛けます。" }, "get-query-type-options": { "description": { - "instant-query-range": "" + "instant-query-range": "インスタントクエリと範囲クエリを実行" }, "label": { - "both": "" + "both": "両方" }, "range-options": { "description": { - "query-range": "" + "query-range": "時間範囲にわたってクエリを実行" }, "label": { - "instant": "", - "range": "" + "instant": "インスタント", + "range": "範囲" } } }, "label-selector": { - "aria-label-filter-expression-for-label": "", - "description-select-labels": "", - "select-labels-to-search-in": "" + "aria-label-filter-expression-for-label": "ラベルのフィルター式", + "description-select-labels": "ラベル値を選択すると、可能なラベルの組み合わせのみが表示されます。", + "select-labels-to-search-in": "2. 検索するラベルを選択" }, "metric-selector": { - "aria-label-filter-expression-for-metric": "", - "aria-label-limit-results-from-series-endpoint": "", - "description-series-limit": "", - "label-select-metric": "", - "select-a-metric": "", - "series-limit": "" + "aria-label-filter-expression-for-metric": "メトリックのフィルター式", + "aria-label-limit-results-from-series-endpoint": "系列エンドポイントからの結果を制限", + "description-series-limit": "制限はすべてのメトリック、ラベル、値に適用されます。デフォルトの制限を使用するには、フィールドを空のままにしてください。制限を無効にしてすべてを取得するには0に設定します。これによりパフォーマンスの問題が発生する可能性があります。", + "label-select-metric": "メトリックを選択すると、可能なラベルのみが表示されます。ラベルには以下の系列制限が適用されます。", + "select-a-metric": "メトリックを選択", + "series-limit": "系列制限" }, "prom-cheat-sheet": { - "prom-ql-cheat-sheet": "" + "prom-ql-cheat-sheet": "PromQLチートシート" }, "prom-exemplar-field": { - "exemplars": "", - "tooltip-disable-query": "", - "tooltip-enable-query": "" + "exemplars": "エグゼンプラー", + "tooltip-disable-query": "エグゼンプラーを使用したクエリを無効化", + "tooltip-enable-query": "エグゼンプラーを使用したクエリを有効化" }, "prom-explore-extra-field": { - "aria-label-prometheus-extra-field": "", - "aria-label-query-type-field": "", - "aria-label-step-field": "", - "min-step": "", - "query-type": "", - "tooltip-units-builtin-variables-example-interval-rateinterval": "" + "aria-label-prometheus-extra-field": "Prometheus追加フィールド", + "aria-label-query-type-field": "クエリタイプフィールド", + "aria-label-step-field": "ステップフィールド", + "min-step": "最小ステップ", + "query-type": "クエリタイプ", + "tooltip-units-builtin-variables-example-interval-rateinterval": "ここでは時間単位と組み込み変数を使用できます。例: {{example1}}、{{example2}}、{{example3}}、{{example4}}、{{example5}}、{{example6}}、{{example7}}(単位が指定されていない場合のデフォルト: {{default}})" }, "prom-query-field": { - "placeholder-enter-a-prom-ql-query": "" + "placeholder-enter-a-prom-ql-query": "PromQLクエリを入力…" }, "prom-variable-query-editor": { - "aria-label-classic-query": "", - "aria-label-metric-regex": "", - "aria-label-metric-selector": "", - "aria-label-prometheus-query": "", - "aria-label-query-type": "", - "aria-label-series-query": "", - "label-classic-query": "", + "aria-label-classic-query": "クラシッククエリ", + "aria-label-metric-regex": "メトリック正規表現", + "aria-label-metric-selector": "メトリックセレクター", + "aria-label-prometheus-query": "Prometheusクエリ", + "aria-label-query-type": "クエリタイプ", + "aria-label-series-query": "系列クエリ", + "label-classic-query": "クラシッククエリ", "label-label": "ラベル", - "label-metric-regex": "", + "label-metric-regex": "メトリック正規表現", "label-query": "クエリ", - "label-query-type": "", - "label-series-query": "", - "placeholder-classic-query": "", - "placeholder-metric-regex": "", - "placeholder-prometheus-query": "", + "label-query-type": "クエリタイプ", + "label-series-query": "系列クエリ", + "placeholder-classic-query": "クラシッククエリ", + "placeholder-metric-regex": "メトリック正規表現", + "placeholder-prometheus-query": "Prometheusクエリ", "placeholder-select-query-type": "クエリタイプを選択", - "placeholder-series-query": "", - "returns-metrics-matching-specified-metric-regex": "", - "tooltip-classic-query": "", - "tooltip-label": "", - "tooltip-metric-regex": "", - "tooltip-query": "", - "tooltip-query-type": "", - "tooltip-series-query": "" + "placeholder-series-query": "系列クエリ", + "returns-metrics-matching-specified-metric-regex": "指定された正規表現に一致するメトリックのリストを返します。", + "tooltip-classic-query": "Prometheus変数クエリエディターの元の実装。これらのドキュメントに記載されているように、正しいクエリタイプとパラメーターを含む文字列を入力してください。例: {{exampleQuery}}。", + "tooltip-label": "メトリックが指定されていない限り、すべてのメトリックのラベル名に対するラベル値のリストを返します。", + "tooltip-metric-regex": "ラベル名のリストを返します。オプションで、指定されたメトリック正規表現でフィルタリングすることも可能です。", + "tooltip-query": "クエリのPrometheusクエリ結果のリストを返します。これには、Prometheus関数を含めることもできます。例: {{exampleQuery}}。", + "tooltip-query-type": "Prometheusデータソースプラグインは、テンプレート変数に以下のクエリタイプを入力します。", + "tooltip-series-query": "ラベル付きメトリック、メトリックのみ、またはラベルのみを入力します。例: {{example1}}、{{example2}}、または{{example3}}。入力されたデータに関連する時系列のリストを返します。" }, "selector-actions": { - "aria-label-selector": "", - "aria-label-selector-clear-button": "", - "aria-label-use-selector-as-metrics-button": "", - "aria-label-use-selector-for-query-button": "", - "aria-label-validate-submit-button": "", + "aria-label-selector": "セレクター", + "aria-label-selector-clear-button": "セレクタークリアボタン", + "aria-label-use-selector-as-metrics-button": "メトリックボタンにセレクターを使用", + "aria-label-use-selector-for-query-button": "クエリボタンにセレクターを使用", + "aria-label-validate-submit-button": "送信ボタンを検証", "clear": "消去", - "resulting-selector": "", - "use-as-rate-query": "", - "use-query": "", - "validate-selector": "" + "resulting-selector": "4. 結果セレクター", + "use-as-rate-query": "レートクエリとして使用", + "use-query": "このクエリを使用", + "validate-selector": "セレクターを検証" }, "value-selector": { - "aria-label-filter-expression-for-label-values": "", - "aria-label-values-for": "", - "description-search-field-values-across-selected-labels": "", - "select-multiple-values-for-your-labels": "" + "aria-label-filter-expression-for-label-values": "ラベル値のフィルター式", + "aria-label-values-for": "{{labelKey}}の値", + "description-search-field-values-across-selected-labels": "検索フィールドを使用して選択したラベル全体の値を検索します。", + "select-multiple-values-for-your-labels": "3. ラベルの値を(複数)選択" } }, "configuration": { "alerting-settings-overhaul": { - "label-allow-as-recording-rules-target": "", - "label-manage-alerts-via-alerting-ui": "", + "label-allow-as-recording-rules-target": "記録ルールのターゲットとして許可", + "label-manage-alerts-via-alerting-ui": "アラートUIでアラートを管理", "title-alerting": "アラート", - "tooltip-allow-as-recording-rules-target": "", + "tooltip-allow-as-recording-rules-target": "このデータソースを記録ルール書き込みのターゲットとして選択できるようにします。", "tooltip-manage-alerts-via-alerting-ui": "このデータソースのアラートルールを管理します。他のアラートリソースを管理するには、Alertmanagerデータソースを追加します。" }, "config-editor": { - "browser-access-mode-error": "", - "description-advanced-settings": "", + "browser-access-mode-error": "Prometheusデータソースのブラウザアクセスモードは利用できなくなりました。サーバーアクセスモードに切り替えてください。", + "description-advanced-settings": "追加設定は、データソースをより詳細に制御するために指定できるオプション設定です。", "title-advanced-settings": "詳細設定", "title-error": "エラー" }, "data-source-http-settings-overhaul": { "tooltip-browser-access-mode": "アクセス方法は<1>ブラウザです。つまり、URLにブラウザからアクセスできる必要があります。", - "tooltip-http-url": "", + "tooltip-http-url": "完全なHTTP URLを指定してください(例: {{exampleURL}})", "tooltip-server-access-mode": "アクセス方法は<1>サーバーです。つまり、URLにGrafanaバックエンド/サーバーからアクセスできる必要があります。" }, "docs-tip": { - "visit-docs-for-more-details-here": "" + "visit-docs-for-more-details-here": "詳細についてはこちらのドキュメントをご覧ください。" }, "exemplar-setting": { "label-data-source": "データソース", - "label-internal-link": "", + "label-internal-link": "内部リンク", "label-label-name": "ラベル名", - "label-remove-exemplar-link": "", + "label-remove-exemplar-link": "エグゼンプラーリンクを削除", "label-url": "URL", - "label-url-label": "", - "placeholder-go-to-examplecom": "", - "placeholder-httpsexamplecomvalueraw": "", - "placeholder-trace-id": "", - "title-remove-exemplar-link": "", - "tooltip-data-source": "", - "tooltip-internal-link": "", - "tooltip-label-name": "", - "tooltip-url": "", - "tooltip-url-label": "" + "label-url-label": "URLラベル", + "placeholder-go-to-examplecom": "example.comに移動", + "placeholder-httpsexamplecomvalueraw": "https://example.com/${__value.raw}", + "placeholder-trace-id": "traceID", + "title-remove-exemplar-link": "エグゼンプラーリンクを削除", + "tooltip-data-source": "エグゼンプラーによる移動先のデータソース。", + "tooltip-internal-link": "内部リンクがある場合は、このオプションを有効にしてください。有効にすると、データソースセレクターが表示されます。エグゼンプラーデータのバックエンドトレーシングデータストアを選択してください。", + "tooltip-label-name": "traceIDを取得するために使用されるラベルオブジェクト内のフィールド名。", + "tooltip-url": "ユーザーがトレースを確認するためにアクセスするトレースバックエンドのURL", + "tooltip-url-label": "エグゼンプラーのtraceIDフィールドのボタンラベルを上書きするために使用します。" }, "exemplars-settings": { "add": "追加", - "no-exemplars-configurations": "", - "title-exemplars": "" + "no-exemplars-configurations": "エグゼンプラー設定がありません", + "title-exemplars": "エグゼンプラー" }, "prom-settings": { - "aria-label-default-editor": "", - "aria-label-prom-type-type": "", - "aria-label-prometheus-type": "", - "aria-label-select-http-method": "", + "aria-label-default-editor": "デフォルトエディター(コードまたはビルダー)", + "aria-label-prom-type-type": "{{promType}}タイプ", + "aria-label-prometheus-type": "Prometheusタイプ", + "aria-label-select-http-method": "HTTPメソッドを選択", "editor-options": { "label-builder": "", "label-code": "" }, - "label-cache-level": "", - "label-custom-query-parameters": "", - "label-default-editor": "", - "label-disable-metrics-lookup": "", - "label-disable-recording-rules-beta": "", - "label-http-method": "", - "label-incremental-querying-beta": "", - "label-metric-names-suggestion-limit": "", - "label-prom-type-version": "", - "label-prometheus-type": "", - "label-query-overlap-window": "", - "label-query-timeout": "", - "label-scrape-interval": "", - "label-series-limit": "", - "label-use-series-endpoint": "", - "more-info": "", - "placeholder-example-maxsourceresolutionmtimeout": "", - "title-interval-behaviour": "", - "title-other": "", + "label-cache-level": "キャッシュレベル", + "label-custom-query-parameters": "カスタムクエリパラメーター", + "label-default-editor": "デフォルトエディター", + "label-disable-metrics-lookup": "メトリック検索を無効化", + "label-disable-recording-rules-beta": "記録ルールを無効化(ベータ版)", + "label-http-method": "HTTPメソッド", + "label-incremental-querying-beta": "増分クエリ(ベータ版)", + "label-metric-names-suggestion-limit": "メトリック名提案制限", + "label-prom-type-version": "{{promType}}バージョン", + "label-prometheus-type": "Prometheusタイプ", + "label-query-overlap-window": "クエリ重複ウィンドウ", + "label-query-timeout": "クエリタイムアウト", + "label-scrape-interval": "スクレイプ間隔", + "label-series-limit": "系列制限", + "label-use-series-endpoint": "系列エンドポイントを使用", + "more-info": "データソースでのPrometheusタイプとバージョンの設定の詳細については、<2>プロビジョニングドキュメントをご覧ください。", + "placeholder-example-maxsourceresolutionmtimeout": "例: {{example}}", + "title-interval-behaviour": "間隔の動作", + "title-other": "その他", "title-performance": "パフォーマンス", - "title-query-editor": "", - "tooltip-cache-level": "", - "tooltip-custom-query-parameters": "", - "tooltip-default-editor": "", - "tooltip-disable-metrics-lookup": "", - "tooltip-disable-recording-rules-beta": "", - "tooltip-http-method": "", - "tooltip-incremental-querying-beta": "", - "tooltip-metric-names-suggestion-limit": "", - "tooltip-prom-type-version": "", - "tooltip-prometheus-type": "", - "tooltip-query-overlap-window": "", - "tooltip-query-timeout": "", - "tooltip-scrape-interval": "", - "tooltip-series-limit": "", - "tooltip-use-series-endpoint": "" + "title-query-editor": "クエリエディター", + "tooltip-cache-level": "エディタークエリのブラウザーキャッシュレベルを設定します。高カーディナリティデータソースには、より高いキャッシュ設定を推奨します。", + "tooltip-custom-query-parameters": "PrometheusクエリURLにカスタムパラメーターを追加します。例: {{example1}}、{{example2}}、{{example3}}、または{{example4}}。複数のパラメーターは{{concatenationChar}}で連結する必要があります。", + "tooltip-default-editor": "このデータソースのすべてのユーザーにデフォルトエディターオプションを設定します。", + "tooltip-disable-metrics-lookup": "このオプションをオンにすると、クエリフィールドのオートコンプリートでメトリック選択とメトリック/ラベルサポートが無効になります。これは、大規模なPrometheusインスタンスをご利用でパフォーマンスの問題がある場合に役立ちます。", + "tooltip-disable-recording-rules-beta": "この機能は記録ルールを無効にします。ダッシュボードのパフォーマンスを向上させるには、この機能をオンにしてください", + "tooltip-http-method": "PrometheusデータソースをクエリするにはPOSTまたはGET HTTPメソッドのいずれかを使用できます。POSTメソッドだと、より大きなクエリを実行できるため、推奨される方法です。Prometheusのバージョンが2.1より古い場合、またはネットワークでPOSTリクエストが制限されている場合は、これをGETに変更してください。", + "tooltip-incremental-querying-beta": "この機能は相対クエリのデフォルト動作が変更され、常にPrometheusインスタンスから新しいデータをリクエストする代わりに、クエリ結果をキャッシュして新しいレコードのみをリクエストするようになります。データベースとネットワークの負荷を軽減するには、これをオンにしてください。", + "tooltip-metric-names-suggestion-limit": "クエリエディターのコードモードでオートコンプリート候補として表示される可能性のあるメトリック名の最大数。", + "tooltip-prom-type-version": "自動設定されていない場合、これを使用して{{promType}}インスタンスのバージョンを設定してください。", + "tooltip-prometheus-type": "Prometheus、Cortex、Mimir、Thanosなど、Prometheusデータベースのタイプに設定してください。このフィールドを変更すると、現在の設定が保存されます。一部のPrometheusのタイプでは、さまざまなAPIがサポートされているか、またはサポートされていない場合があります。例えば、一部のタイプではパフォーマンス向上のためにラベルクエリの正規表現マッチングをサポートしています。また、タイプによってはメタデータ用のAPIがあります。これを正しく設定しないと、メトリックとラベルのクエリ時に異常な動作が発生する可能性があります。正しいタイプを入力するために、Prometheusのドキュメントを確認してください。", + "tooltip-query-overlap-window": "{{example1}}や{{example2}}または{{example3}}のような期間を設定してください。デフォルトは{{default}}です。この期間は、各増分リクエストの期間に追加されます。", + "tooltip-query-timeout": "Prometheusクエリタイムアウトを設定します。", + "tooltip-scrape-interval": "この間隔は、Prometheusがターゲットをスクレイプする頻度です。Prometheus設定ファイルで指定された一般的なスクレイプと評価間隔に設定してください。これをPrometheus設定ファイルの間隔より大きな値に設定すると、Grafanaはこの間隔に従ってデータを評価するため、データポイントが少なくなります。デフォルトは{{default}}です。", + "tooltip-series-limit": "制限は両方のエンドポイント(系列とラベル)のすべてのリソース(メトリック、ラベル、値)に適用されます。デフォルトの制限(40000)を使用するには、フィールドを空のままにしてください。制限を無効にしてすべてを取得するには0に設定します。これによりパフォーマンスの問題が発生する可能性があります。デフォルトの制限は40000です。", + "tooltip-use-series-endpoint": "このオプションをオンにすると、{{exampleParameter}}パラメーターを持つラベル値エンドポイントより、{{exampleParameter}}パラメーターを持つ系列エンドポイントが優先されます。ラベル値エンドポイントの方がパフォーマンスが高いと考えられていますが、ラベル値エンドポイントにはGETメソッドしかないのに対し、系列にはPOSTメソッドがあるため、系列を好むユーザーもいます。" } }, "prom-query-legend-editor": { @@ -209,14 +209,111 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { - "content-filter-metric-names-regex-search-using": "", - "disable-text-wrap": "" + "content-filter-metric-names-regex-search-using": "Prometheus APIの追加呼び出しを使用して、正規表現検索でメトリック名をフィルタリングします。", + "disable-text-wrap": "テキストの折り返しを無効化" }, "feedback-link": { "give-feedback": "フィードバックを送信", - "title-give-feedback": "" + "title-give-feedback": "メトリックエクスプローラーは新機能です。改善点についてお聞かせください" }, "get-collapsed-info": { "exemplars": "", @@ -250,18 +347,18 @@ }, "handle-function": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "クエリ解析が曖昧です。" } }, "label-filter-item": { - "aria-label-remove": "", - "placeholder-select-label": "", + "aria-label-remove": "{{name}}を削除", + "placeholder-select-label": "ラベルを選択", "placeholder-select-value": "値を選択" }, "label-filters": { - "label-filters": "", - "label-label-filters": "", - "tooltip-label-filters": "" + "label-filters": "ラベルフィルター", + "label-label-filters": "ラベルフィルター", + "tooltip-label-filters": "オプション: このクエリタイプのメトリック選択をフィルタリングするために使用します。" }, "label-param-editor": { "loadingMessage-loading-labels": "", @@ -269,91 +366,98 @@ }, "metric-combobox": { "async-select": { - "aria-label-open-metrics-explorer": "", - "placeholder-select-metric": "", - "tooltip-open-metrics-explorer": "" + "aria-label-open-metrics-explorer": "メトリックエクスプローラーを開く", + "placeholder-select-metric": "メトリックを選択", + "tooltip-open-metrics-explorer": "メトリックエクスプローラーを開く" }, "label-metric": "メトリック", - "tooltip-metric": "" + "tooltip-metric": "オプション: 指定されたメトリックのラベル名に対するラベル値のリストを返します。" }, "metrics-modal": { - "additional-settings": "", + "additional-settings": "追加設定", "aria-label-additional-settings": "追加設定", - "aria-label-browse-metrics": "", - "currently-selected": "", - "metrics-pre-filtered": "", - "placeholder-results-per-page": "", - "results-per-page": "", - "title-metrics-explorer": "", - "results-amount_other": "" + "aria-label-browse-metrics": "メトリックを参照", + "currently-selected": "現在選択中: {{selected}}", + "metrics-pre-filtered": "これらのメトリックは、ラベルフィルターで選択されたラベルによって事前にフィルタリングされています。", + "placeholder-results-per-page": "1ページあたりの結果数", + "results-per-page": "1ページあたりの結果数", + "title-metrics-explorer": "メトリックエクスプローラー", + "results-amount_other": "{{count}}件中{{num}}件の結果を表示" }, "nested-query": { "label": { - "ignoring": "", + "ignoring": "無視", "on": "オン" }, "operator": "オペレーター", - "tooltip-remove-match": "", - "vector-matches": "" + "tooltip-remove-match": "一致を削除", + "vector-matches": "ベクトル一致" }, "operation-editor": { - "not-found": "", - "title-remove": "" + "not-found": "操作{{id}}が見つかりません", + "title-remove": "{{name}}を削除" }, "operation-header": { "placeholder-replace-with": "次で置き換え", - "title-click-to-view-alternative-operations": "", - "title-remove-operation": "" + "title-click-to-view-alternative-operations": "クリックして代わりの操作を表示", + "title-remove-operation": "操作を削除" }, "operation-info-button": { - "title-click-to-show-description": "", - "title-remove-operation": "" + "title-click-to-show-description": "クリックして説明を表示", + "title-remove-operation": "操作を削除" }, "operation-list": { - "operations": "", + "operations": "操作", "placeholder-search": "検索", - "title-add-operation": "" + "title-add-operation": "操作を追加" }, "operation-param-editor": { - "title-add": "", - "title-remove": "" + "title-add": "{{name}}を追加", + "title-remove": "{{name}}を削除" + }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } }, "prom-query-builder-options": { - "aria-label-lower-limit-parameter": "", - "aria-label-select-resolution": "", + "aria-label-lower-limit-parameter": "ステップパラメーターの下限を設定", + "aria-label-select-resolution": "解像度を選択", "format-options": { "label-heatmap": "", "label-table": "", "label-time-series": "" }, - "label-exemplars": "", + "label-exemplars": "エグゼンプラー", "label-format": "形式", - "label-min-step": "", - "label-resolution": "", + "label-min-step": "最小ステップ", + "label-resolution": "解像度", "label-type": "種類", - "placeholder-auto": "", + "placeholder-auto": "自動", "title-options": "オプション", - "tooltip-min-step": "" + "tooltip-min-step": "Prometheusクエリのステップパラメーターと<2>{{interval}}および<4>{{rateInterval}}変数の追加下限。" }, "prom-query-code-editor-autocomplete-info": { - "autocomplete-suggestions-limited": "", - "tooltip-autocomplete-suggestions-limited": "" + "autocomplete-suggestions-limited": "オートコンプリート候補が制限されています", + "tooltip-autocomplete-suggestions-limited": "メトリック名の数がオートコンプリート制限を超えています。最も関連性の高い{{autocompleteLimit}}個のメトリックのみが表示されます。データソース設定で閾値を調整できます。" }, "prom-query-editor-selector": { "body-syntax-error": "", "confirmText-continue": "", "kick-start-your-query": "クエリを開始", - "label-explain": "", - "run-queries": "", - "title-parsing-error-switch-builder": "" + "label-explain": "説明", + "run-queries": "クエリを実行", + "title-parsing-error-switch-builder": "解析エラー: ビルダーモードに切り替えますか?" }, "prom-query-legend-editor": { - "label-legend": "", - "placeholder-select-legend-mode": "", - "tooltip-legend": "" + "label-legend": "凡例", + "placeholder-select-legend-mode": "凡例モードを選択", + "tooltip-legend": "系列名の上書きまたはテンプレート。例: {{templateExample}}は{{labelName}}のラベル値に置き換えられます。" }, "query-builder-hints": { - "hint-details": "" + "hint-details": "ヒント: {{hintDetails}}" }, "query-editor-mode-toggle": { "editor-modes": { @@ -362,30 +466,30 @@ } }, "query-pattern": { - "apply-query": "", - "aria-label-apply-query-starter-button": "", - "aria-label-back-button": "", - "aria-label-create-new-query-button": "", - "aria-label-raw-query": "", - "aria-label-use-this-query-button": "", + "apply-query": "クエリを適用", + "aria-label-apply-query-starter-button": "クエリ適用開始ボタン", + "aria-label-back-button": "戻るボタン", + "aria-label-create-new-query-button": "新しいクエリ作成ボタン", + "aria-label-raw-query": "{{patternName}}の生クエリ", + "aria-label-use-this-query-button": "このクエリボタンを使用", "back": "戻る", - "create-new-query": "", + "create-new-query": "新しいクエリを作成", "use-this-query": "このクエリを使用" }, "query-patterns-modal": { - "aria-label-close-kick-start-your-query-modal": "", + "aria-label-close-kick-start-your-query-modal": "クエリを開始モーダルを閉じる", "aria-label-kick-start-your-query-modal": "クエリモーダルを開始", - "aria-label-toggle-query-starter": "", + "aria-label-toggle-query-starter": "{{patternType}}クエリ開始カードを開いて閉じる", "close": "閉じる", - "description-kick-start-your-query": "", - "label-toggle-query-starter": "", + "description-kick-start-your-query": "これらのクエリの1つを選択してクエリを開始してください。その後、クエリを完了できます。", + "label-toggle-query-starter": "{{patternType}}クエリスターター", "title-kick-start-your-query": "クエリを開始" }, "raw-query": { - "aria-label-selector": "" + "aria-label-selector": "セレクター" }, "results-table": { - "content-descriptive-type": "", + "content-descriptive-type": "{{descriptiveType}}を作成する際、Prometheusはタイプカウンターで複数の系列を公開します。", "description": "説明", "message-expand-label-filters": "", "message-expand-search": "", @@ -396,7 +500,7 @@ }, "update-function-args": { "text": { - "query-parsing-is-ambiguous": "" + "query-parsing-is-ambiguous": "クエリ解析が曖昧です。" } } } diff --git a/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json index 26c7b27eb2f..0fdb0e6d527 100644 --- a/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -318,6 +415,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json index d6bbd899aa2..51074f0b25d 100644 --- a/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "Filter metrische namen op regex-zoekopdracht, met behulp van een extra oproep op de Prometheus-API.", @@ -319,6 +416,13 @@ "title-add": "{{name}} toevoegen", "title-remove": "{{name}} verwijderen" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "Ondergrens instellen voor de stapparameter", "aria-label-select-resolution": "Resolutie selecteren", diff --git a/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json index 38aacf94629..25ef725f484 100644 --- a/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -321,6 +418,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json index 775cb9f3cc3..74e177550c6 100644 --- a/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json index 5d8aa3f0402..93d395b3f57 100644 --- a/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json index e593d2333aa..265b74d6ac3 100644 --- a/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -321,6 +418,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json index 7e50b6aa9ab..7e57336aaca 100644 --- a/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json index 46d4ce11257..b834bace3db 100644 --- a/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "", @@ -319,6 +416,13 @@ "title-add": "", "title-remove": "" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "", "aria-label-select-resolution": "", diff --git a/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json index 9bb70f50992..242003931cd 100644 --- a/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "使用 Prometheus API 上的其他调用,通过正则表达式搜索筛选指标名称。", @@ -318,6 +415,13 @@ "title-add": "添加{{name}}", "title-remove": "移除{{name}}" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "设置步长参数的下限", "aria-label-select-resolution": "选择分辨率", diff --git a/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json index 80a90677692..81bdc65f727 100644 --- a/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json @@ -209,6 +209,103 @@ "label-verbose": "" } }, + "promql": { + "getAggregationOptions": { + "documentation-avg": "", + "documentation-bottomk": "", + "documentation-count": "", + "documentation-count-values": "", + "documentation-group": "", + "documentation-max": "", + "documentation-min": "", + "documentation-quantile": "", + "documentation-stddev": "", + "documentation-stdvar": "", + "documentation-sum": "", + "documentation-topk": "" + }, + "getFunctions": { + "documentation-abs": "", + "documentation-absent": "", + "documentation-absent-over-time": "", + "documentation-avg-over-time": "", + "documentation-ceil": "", + "documentation-changes": "", + "documentation-clamp": "", + "documentation-clamp-max": "", + "documentation-clamp-min": "", + "documentation-count-over-time": "", + "documentation-count-scalar": "", + "documentation-day-of-month": "", + "documentation-day-of-week": "", + "documentation-day-of-year": "", + "documentation-days-in-month": "", + "documentation-deg": "", + "documentation-delta": "", + "documentation-deriv": "", + "documentation-double-exponential-smoothing": "", + "documentation-drop-common-labels": "", + "documentation-exp": "", + "documentation-floor": "", + "documentation-histogram-avg": "", + "documentation-histogram-count": "", + "documentation-histogram-fraction": "", + "documentation-histogram-quantile": "", + "documentation-histogram-stddev": "", + "documentation-histogram-stdvar": "", + "documentation-histogram-sum": "", + "documentation-holt-winters": "", + "documentation-hour": "", + "documentation-idelta": "", + "documentation-increase": "", + "documentation-info": "", + "documentation-irate": "", + "documentation-label-join": "", + "documentation-label-replace": "", + "documentation-last-over-time": "", + "documentation-ln": "", + "documentation-log10": "", + "documentation-log2": "", + "documentation-max-over-time": "", + "documentation-min-over-time": "", + "documentation-minute": "", + "documentation-month": "", + "documentation-pi": "", + "documentation-predict-linear": "", + "documentation-present-over-time": "", + "documentation-quantile-over-time": "", + "documentation-rad": "", + "documentation-rate": "", + "documentation-resets": "", + "documentation-round": "", + "documentation-scalar": "", + "documentation-sgn": "", + "documentation-sort": "", + "documentation-sort-desc": "", + "documentation-sqrt": "", + "documentation-stddev-over-time": "", + "documentation-stdvar-over-time": "", + "documentation-sum-over-time": "", + "documentation-time": "", + "documentation-timestamp": "", + "documentation-vector": "", + "documentation-year": "" + }, + "getTrigonometricFunctions": { + "documentation-acos": "", + "documentation-acosh": "", + "documentation-asin": "", + "documentation-asinh": "", + "documentation-atan": "", + "documentation-atanh": "", + "documentation-cos": "", + "documentation-cosh": "", + "documentation-sin": "", + "documentation-sinh": "", + "documentation-tan": "", + "documentation-tanh": "" + } + }, "querybuilder": { "additional-settings": { "content-filter-metric-names-regex-search-using": "透過正規表達式搜尋篩選指標名稱,並使用額外的 Prometheus API 呼叫。", @@ -318,6 +415,13 @@ "title-add": "新增{{name}}", "title-remove": "移除{{name}}" }, + "operation-utils": { + "getAggregationExplainer": { + "label-by": "", + "label-default": "", + "label-without": "" + } + }, "prom-query-builder-options": { "aria-label-lower-limit-parameter": "設定步驟參數的下限", "aria-label-select-resolution": "選擇解決方案", diff --git a/packages/grafana-prometheus/src/promql.ts b/packages/grafana-prometheus/src/promql.ts index 9235777c6a5..d3e6304312b 100644 --- a/packages/grafana-prometheus/src/promql.ts +++ b/packages/grafana-prometheus/src/promql.ts @@ -1,577 +1,819 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/promql.ts import { Grammar } from 'prismjs'; +import { t } from '@grafana/i18n'; import { CompletionItem } from '@grafana/ui'; export const OPERATORS = ['by', 'group_left', 'group_right', 'ignoring', 'on', 'offset', 'without']; export const LOGICAL_OPERATORS = ['or', 'and', 'unless']; -const TRIGONOMETRIC_FUNCTIONS: CompletionItem[] = [ +const getTrigonometricFunctions: () => CompletionItem[] = () => [ { label: 'acos', insertText: 'acos', detail: 'acos(v instant-vector)', - documentation: 'calculates the arccosine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-acos', + 'calculates the arccosine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'acosh', insertText: 'acosh', detail: 'acosh(v instant-vector)', - documentation: 'calculates the inverse hyperbolic cosine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-acosh', + 'calculates the inverse hyperbolic cosine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'asin', insertText: 'asin', detail: 'asin(v instant-vector)', - documentation: 'calculates the arcsine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-asin', + 'calculates the arcsine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'asinh', insertText: 'asinh', detail: 'asinh(v instant-vector)', - documentation: 'calculates the inverse hyperbolic sine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-asinh', + 'calculates the inverse hyperbolic sine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'atan', insertText: 'atan', detail: 'atan(v instant-vector)', - documentation: 'calculates the arctangent of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-atan', + 'calculates the arctangent of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'atanh', insertText: 'atanh', detail: 'atanh(v instant-vector)', - documentation: 'calculates the inverse hyperbolic tangent of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-atanh', + 'calculates the inverse hyperbolic tangent of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'cos', insertText: 'cos', detail: 'cos(v instant-vector)', - documentation: 'calculates the cosine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-cos', + 'calculates the cosine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'cosh', insertText: 'cosh', detail: 'cosh(v instant-vector)', - documentation: 'calculates the hyperbolic cosine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-cosh', + 'calculates the hyperbolic cosine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'sin', insertText: 'sin', detail: 'sin(v instant-vector)', - documentation: 'calculates the sine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-sin', + 'calculates the sine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'sinh', insertText: 'sinh', detail: 'sinh(v instant-vector)', - documentation: 'calculates the hyperbolic sine of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-sinh', + 'calculates the hyperbolic sine of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'tan', insertText: 'tan', detail: 'tan(v instant-vector)', - documentation: 'calculates the tangent of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-tan', + 'calculates the tangent of all elements in {{argument}}', + { argument: 'v' } + ), }, { label: 'tanh', insertText: 'tanh', detail: 'tanh(v instant-vector)', - documentation: 'calculates the hyperbolic tangent of all elements in v', + documentation: t( + 'grafana-prometheus.promql.getTrigonometricFunctions.documentation-tanh', + 'calculates the hyperbolic tangent of all elements in {{argument}}', + { argument: 'v' } + ), }, ]; -const AGGREGATION_OPERATORS: CompletionItem[] = [ +const getAggregationOptions: () => CompletionItem[] = () => [ { label: 'sum', insertText: 'sum', - documentation: 'Calculate sum over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-sum', + 'Calculate sum over dimensions' + ), }, { label: 'min', insertText: 'min', - documentation: 'Select minimum over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-min', + 'Select minimum over dimensions' + ), }, { label: 'max', insertText: 'max', - documentation: 'Select maximum over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-max', + 'Select maximum over dimensions' + ), }, { label: 'avg', insertText: 'avg', - documentation: 'Calculate the average over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-avg', + 'Calculate the average over dimensions' + ), }, { label: 'group', insertText: 'group', - documentation: 'All values in the resulting vector are 1', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-group', + 'All values in the resulting vector are 1' + ), }, { label: 'stddev', insertText: 'stddev', - documentation: 'Calculate population standard deviation over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-stddev', + 'Calculate population standard deviation over dimensions' + ), }, { label: 'stdvar', insertText: 'stdvar', - documentation: 'Calculate population standard variance over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-stdvar', + 'Calculate population standard variance over dimensions' + ), }, { label: 'count', insertText: 'count', - documentation: 'Count number of elements in the vector', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-count', + 'Count number of elements in the vector' + ), }, { label: 'count_values', insertText: 'count_values', - documentation: 'Count number of elements with the same value', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-count-values', + 'Count number of elements with the same value' + ), }, { label: 'bottomk', insertText: 'bottomk', - documentation: 'Smallest k elements by sample value', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-bottomk', + 'Smallest k elements by sample value' + ), }, { label: 'topk', insertText: 'topk', - documentation: 'Largest k elements by sample value', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-topk', + 'Largest k elements by sample value' + ), }, { label: 'quantile', insertText: 'quantile', - documentation: 'Calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions', + documentation: t( + 'grafana-prometheus.promql.getAggregationOptions.documentation-quantile', + 'Calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions' + ), }, ]; -export const FUNCTIONS = [ - ...AGGREGATION_OPERATORS, - ...TRIGONOMETRIC_FUNCTIONS, +export const getFunctions = () => [ + ...getAggregationOptions(), + ...getTrigonometricFunctions(), { insertText: 'abs', label: 'abs', detail: 'abs(v instant-vector)', - documentation: 'Returns the input vector with all sample values converted to their absolute value.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-abs', + 'Returns the input vector with all sample values converted to their absolute value.' + ), }, { insertText: 'absent', label: 'absent', detail: 'absent(v instant-vector)', - documentation: - 'Returns an empty vector if the vector passed to it has any elements and a 1-element vector with the value 1 if the vector passed to it has no elements. This is useful for alerting on when no time series exist for a given metric name and label combination.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-absent', + 'Returns an empty vector if the vector passed to it has any elements and a 1-element vector with the value 1 if the vector passed to it has no elements. This is useful for alerting on when no time series exist for a given metric name and label combination.' + ), }, { insertText: 'absent_over_time', label: 'absent_over_time', detail: 'absent(v range-vector)', - documentation: - 'Returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-absent-over-time', + 'Returns an empty vector if the range vector passed to it has any elements and a 1-element vector with the value 1 if the range vector passed to it has no elements.' + ), }, { insertText: 'ceil', label: 'ceil', detail: 'ceil(v instant-vector)', - documentation: 'Rounds the sample values of all elements in `v` up to the nearest integer.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-ceil', + 'Rounds the sample values of all elements in `v` up to the nearest integer.' + ), }, { insertText: 'changes', label: 'changes', detail: 'changes(v range-vector)', - documentation: - 'For each input time series, `changes(v range-vector)` returns the number of times its value has changed within the provided time range as an instant vector.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-changes', + 'For each input time series, `changes(v range-vector)` returns the number of times its value has changed within the provided time range as an instant vector.' + ), }, { insertText: 'clamp', label: 'clamp', detail: 'clamp(v instant-vector, min scalar, max scalar)', - documentation: - 'Clamps the sample values of all elements in `v` to have a lower limit of `min` and an upper limit of `max`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-clamp', + 'Clamps the sample values of all elements in `v` to have a lower limit of `min` and an upper limit of `max`.' + ), }, { insertText: 'clamp_max', label: 'clamp_max', detail: 'clamp_max(v instant-vector, max scalar)', - documentation: 'Clamps the sample values of all elements in `v` to have an upper limit of `max`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-clamp-max', + 'Clamps the sample values of all elements in `v` to have an upper limit of `max`.' + ), }, { insertText: 'clamp_min', label: 'clamp_min', detail: 'clamp_min(v instant-vector, min scalar)', - documentation: 'Clamps the sample values of all elements in `v` to have a lower limit of `min`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-clamp-min', + 'Clamps the sample values of all elements in `v` to have a lower limit of `min`.' + ), }, { insertText: 'count_scalar', label: 'count_scalar', detail: 'count_scalar(v instant-vector)', - documentation: - 'Returns the number of elements in a time series vector as a scalar. This is in contrast to the `count()` aggregation operator, which always returns a vector (an empty one if the input vector is empty) and allows grouping by labels via a `by` clause.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-count-scalar', + 'Returns the number of elements in a time series vector as a scalar. This is in contrast to the `count()` aggregation operator, which always returns a vector (an empty one if the input vector is empty) and allows grouping by labels via a `by` clause.' + ), }, { insertText: 'deg', label: 'deg', detail: 'deg(v instant-vector)', - documentation: 'Converts radians to degrees for all elements in v', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-deg', + 'Converts radians to degrees for all elements in v' + ), }, { insertText: 'day_of_month', label: 'day_of_month', detail: 'day_of_month(v=vector(time()) instant-vector)', - documentation: 'Returns the day of the month for each of the given times in UTC. Returned values are from 1 to 31.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-day-of-month', + 'Returns the day of the month for each of the given times in UTC. Returned values are from 1 to 31.' + ), }, { insertText: 'day_of_week', label: 'day_of_week', detail: 'day_of_week(v=vector(time()) instant-vector)', - documentation: - 'Returns the day of the week for each of the given times in UTC. Returned values are from 0 to 6, where 0 means Sunday etc.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-day-of-week', + 'Returns the day of the week for each of the given times in UTC. Returned values are from 0 to 6, where 0 means Sunday etc.' + ), }, { insertText: 'day_of_year', label: 'day_of_year', detail: 'day_of_year(v=vector(time()) instant-vector)', - documentation: - 'Returns the day of the year for each of the given times in UTC. Returned values are from 1 to 365 for non-leap years, and 1 to 366 in leap years.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-day-of-year', + 'Returns the day of the year for each of the given times in UTC. Returned values are from 1 to 365 for non-leap years, and 1 to 366 in leap years.' + ), }, { insertText: 'days_in_month', label: 'days_in_month', detail: 'days_in_month(v=vector(time()) instant-vector)', - documentation: - 'Returns number of days in the month for each of the given times in UTC. Returned values are from 28 to 31.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-days-in-month', + 'Returns number of days in the month for each of the given times in UTC. Returned values are from 28 to 31.' + ), }, { insertText: 'delta', label: 'delta', detail: 'delta(v range-vector)', - documentation: - 'Calculates the difference between the first and last value of each time series element in a range vector `v`, returning an instant vector with the given deltas and equivalent labels. The delta is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if the sample values are all integers.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-delta', + 'Calculates the difference between the first and last value of each time series element in a range vector `v`, returning an instant vector with the given deltas and equivalent labels. The delta is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if the sample values are all integers.' + ), }, { insertText: 'deriv', label: 'deriv', detail: 'deriv(v range-vector)', - documentation: - 'Calculates the per-second derivative of the time series in a range vector `v`, using simple linear regression.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-deriv', + 'Calculates the per-second derivative of the time series in a range vector `v`, using simple linear regression.' + ), }, { insertText: 'double_exponential_smoothing', label: 'double_exponential_smoothing', detail: 'double_exponential_smoothing(v range-vector, sf scalar, tf scalar)', - documentation: - 'Produces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-double-exponential-smoothing', + 'Produces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.' + ), }, { insertText: 'drop_common_labels', label: 'drop_common_labels', detail: 'drop_common_labels(instant-vector)', - documentation: 'Drops all labels that have the same name and value across all series in the input vector.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-drop-common-labels', + 'Drops all labels that have the same name and value across all series in the input vector.' + ), }, { insertText: 'exp', label: 'exp', detail: 'exp(v instant-vector)', - documentation: - 'Calculates the exponential function for all elements in `v`.\nSpecial cases are:\n* `Exp(+Inf) = +Inf` \n* `Exp(NaN) = NaN`', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-exp', + 'Calculates the exponential function for all elements in `v`.\nSpecial cases are:\n* `Exp(+Inf) = +Inf` \n* `Exp(NaN) = NaN`' + ), }, { insertText: 'floor', label: 'floor', detail: 'floor(v instant-vector)', - documentation: 'Rounds the sample values of all elements in `v` down to the nearest integer.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-floor', + 'Rounds the sample values of all elements in `v` down to the nearest integer.' + ), }, { insertText: 'histogram_quantile', label: 'histogram_quantile', detail: 'histogram_quantile(φ float, b instant-vector)', - documentation: - 'Calculates the φ-quantile (0 ≤ φ ≤ 1) from the buckets `b` of a histogram. The samples in `b` are the counts of observations in each bucket. Each sample must have a label `le` where the label value denotes the inclusive upper bound of the bucket. (Samples without such a label are silently ignored.) The histogram metric type automatically provides time series with the `_bucket` suffix and the appropriate labels.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-quantile', + 'Calculates the φ-quantile (0 ≤ φ ≤ 1) from the buckets `b` of a histogram. The samples in `b` are the counts of observations in each bucket. Each sample must have a label `le` where the label value denotes the inclusive upper bound of the bucket. (Samples without such a label are silently ignored.) The histogram metric type automatically provides time series with the `_bucket` suffix and the appropriate labels.' + ), }, { insertText: 'holt_winters', label: 'holt_winters', detail: 'holt_winters(v range-vector, sf scalar, tf scalar)', - documentation: - 'Renamed as double_exponential_smoothing in prometheus v3.x. For prometheus versions equal and greater than v3.0 please use double_exponential_smoothing. \n\nProduces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-holt-winters', + 'Renamed as double_exponential_smoothing in prometheus v3.x. For prometheus versions equal and greater than v3.0 please use double_exponential_smoothing. \n\nProduces a smoothed value for time series based on the range in `v`. The lower the smoothing factor `sf`, the more importance is given to old data. The higher the trend factor `tf`, the more trends in the data is considered. Both `sf` and `tf` must be between 0 and 1.' + ), }, { insertText: 'hour', label: 'hour', detail: 'hour(v=vector(time()) instant-vector)', - documentation: 'Returns the hour of the day for each of the given times in UTC. Returned values are from 0 to 23.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-hour', + 'Returns the hour of the day for each of the given times in UTC. Returned values are from 0 to 23.' + ), }, { insertText: 'idelta', label: 'idelta', detail: 'idelta(v range-vector)', - documentation: - 'Calculates the difference between the last two samples in the range vector `v`, returning an instant vector with the given deltas and equivalent labels.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-idelta', + 'Calculates the difference between the last two samples in the range vector `v`, returning an instant vector with the given deltas and equivalent labels.' + ), }, { insertText: 'increase', label: 'increase', detail: 'increase(v range-vector)', - documentation: - 'Calculates the increase in the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-increase', + 'Calculates the increase in the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. The increase is extrapolated to cover the full time range as specified in the range vector selector, so that it is possible to get a non-integer result even if a counter increases only by integer increments.' + ), }, { insertText: 'info', label: 'info', detail: 'info(v instant-vector, [data-label-selector instant-vector])', - documentation: - 'Returns latest details and metadata about a group of metrics, such as their labels and current values, without doing any calculations', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-info', + 'Returns latest details and metadata about a group of metrics, such as their labels and current values, without doing any calculations' + ), }, { insertText: 'irate', label: 'irate', detail: 'irate(v range-vector)', - documentation: - 'Calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-irate', + 'Calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for.' + ), }, { insertText: 'label_join', label: 'label_join', detail: 'label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)', - documentation: - 'For each timeseries in `v`, joins all the values of all the `src_labels` using `separator` and returns the timeseries with the label `dst_label` containing the joined value. There can be any number of `src_labels` in this function.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-label-join', + 'For each timeseries in `v`, joins all the values of all the `src_labels` using `separator` and returns the timeseries with the label `dst_label` containing the joined value. There can be any number of `src_labels` in this function.' + ), }, { insertText: 'label_replace', label: 'label_replace', detail: 'label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)', - documentation: - "For each timeseries in `v`, `label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)` matches the regular expression `regex` against the label `src_label`. If it matches, then the timeseries is returned with the label `dst_label` replaced by the expansion of `replacement`. `$1` is replaced with the first matching subgroup, `$2` with the second etc. If the regular expression doesn't match then the timeseries is returned unchanged.", + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-label-replace', + "For each timeseries in `v`, `label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)` matches the regular expression `regex` against the label `src_label`. If it matches, then the timeseries is returned with the label `dst_label` replaced by the expansion of `replacement`. `$1` is replaced with the first matching subgroup, `$2` with the second etc. If the regular expression doesn't match then the timeseries is returned unchanged." + ), }, { insertText: 'ln', label: 'ln', detail: 'ln(v instant-vector)', - documentation: - 'Calculates the natural logarithm for all elements in `v`.\nSpecial cases are:\n * `ln(+Inf) = +Inf`\n * `ln(0) = -Inf`\n * `ln(x < 0) = NaN`\n * `ln(NaN) = NaN`', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-ln', + 'Calculates the natural logarithm for all elements in `v`.\nSpecial cases are:\n * `ln(+Inf) = +Inf`\n * `ln(0) = -Inf`\n * `ln(x < 0) = NaN`\n * `ln(NaN) = NaN`' + ), }, { insertText: 'log2', label: 'log2', detail: 'log2(v instant-vector)', - documentation: - 'Calculates the binary logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-log2', + 'Calculates the binary logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.' + ), }, { insertText: 'log10', label: 'log10', detail: 'log10(v instant-vector)', - documentation: - 'Calculates the decimal logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-log10', + 'Calculates the decimal logarithm for all elements in `v`. The special cases are equivalent to those in `ln`.' + ), }, { insertText: 'minute', label: 'minute', detail: 'minute(v=vector(time()) instant-vector)', - documentation: - 'Returns the minute of the hour for each of the given times in UTC. Returned values are from 0 to 59.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-minute', + 'Returns the minute of the hour for each of the given times in UTC. Returned values are from 0 to 59.' + ), }, { insertText: 'month', label: 'month', detail: 'month(v=vector(time()) instant-vector)', - documentation: - 'Returns the month of the year for each of the given times in UTC. Returned values are from 1 to 12, where 1 means January etc.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-month', + 'Returns the month of the year for each of the given times in UTC. Returned values are from 1 to 12, where 1 means January etc.' + ), }, { insertText: 'pi', label: 'pi', detail: 'pi()', - documentation: 'Returns pi', + documentation: t('grafana-prometheus.promql.getFunctions.documentation-pi', 'Returns pi'), }, { insertText: 'predict_linear', label: 'predict_linear', detail: 'predict_linear(v range-vector, t scalar)', - documentation: - 'Predicts the value of time series `t` seconds from now, based on the range vector `v`, using simple linear regression.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-predict-linear', + 'Predicts the value of time series `t` seconds from now, based on the range vector `v`, using simple linear regression.' + ), }, { insertText: 'rad', label: 'rad', detail: 'rad(v instant-vector)', - documentation: 'Converts degrees to radians for all elements in v', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-rad', + 'Converts degrees to radians for all elements in v' + ), }, { insertText: 'rate', label: 'rate', detail: 'rate(v range-vector)', - documentation: - "Calculates the per-second average rate of increase of the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. Also, the calculation extrapolates to the ends of the time range, allowing for missed scrapes or imperfect alignment of scrape cycles with the range's time period.", + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-rate', + "Calculates the per-second average rate of increase of the time series in the range vector. Breaks in monotonicity (such as counter resets due to target restarts) are automatically adjusted for. Also, the calculation extrapolates to the ends of the time range, allowing for missed scrapes or imperfect alignment of scrape cycles with the range's time period." + ), }, { insertText: 'resets', label: 'resets', detail: 'resets(v range-vector)', - documentation: - 'For each input time series, `resets(v range-vector)` returns the number of counter resets within the provided time range as an instant vector. Any decrease in the value between two consecutive samples is interpreted as a counter reset.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-resets', + 'For each input time series, `resets(v range-vector)` returns the number of counter resets within the provided time range as an instant vector. Any decrease in the value between two consecutive samples is interpreted as a counter reset.' + ), }, { insertText: 'round', label: 'round', detail: 'round(v instant-vector, to_nearest=1 scalar)', - documentation: - 'Rounds the sample values of all elements in `v` to the nearest integer. Ties are resolved by rounding up. The optional `to_nearest` argument allows specifying the nearest multiple to which the sample values should be rounded. This multiple may also be a fraction.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-round', + 'Rounds the sample values of all elements in `v` to the nearest integer. Ties are resolved by rounding up. The optional `to_nearest` argument allows specifying the nearest multiple to which the sample values should be rounded. This multiple may also be a fraction.' + ), }, { insertText: 'scalar', label: 'scalar', detail: 'scalar(v instant-vector)', - documentation: - 'Given a single-element input vector, `scalar(v instant-vector)` returns the sample value of that single element as a scalar. If the input vector does not have exactly one element, `scalar` will return `NaN`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-scalar', + 'Given a single-element input vector, `scalar(v instant-vector)` returns the sample value of that single element as a scalar. If the input vector does not have exactly one element, `scalar` will return `NaN`.' + ), }, { insertText: 'sgn', label: 'sgn', detail: 'sgn(v instant-vector)', - documentation: - 'Returns a vector with all sample values converted to their sign, defined as this: 1 if v is positive, -1 if v is negative and 0 if v is equal to zero.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-sgn', + 'Returns a vector with all sample values converted to their sign, defined as this: 1 if v is positive, -1 if v is negative and 0 if v is equal to zero.' + ), }, { insertText: 'sort', label: 'sort', detail: 'sort(v instant-vector)', - documentation: 'Returns vector elements sorted by their sample values, in ascending order.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-sort', + 'Returns vector elements sorted by their sample values, in ascending order.' + ), }, { insertText: 'sort_desc', label: 'sort_desc', detail: 'sort_desc(v instant-vector)', - documentation: 'Returns vector elements sorted by their sample values, in descending order.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-sort-desc', + 'Returns vector elements sorted by their sample values, in descending order.' + ), }, { insertText: 'sqrt', label: 'sqrt', detail: 'sqrt(v instant-vector)', - documentation: 'Calculates the square root of all elements in `v`.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-sqrt', + 'Calculates the square root of all elements in `v`.' + ), }, { insertText: 'time', label: 'time', detail: 'time()', - documentation: - 'Returns the number of seconds since January 1, 1970 UTC. Note that this does not actually return the current time, but the time at which the expression is to be evaluated.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-time', + 'Returns the number of seconds since January 1, 1970 UTC. Note that this does not actually return the current time, but the time at which the expression is to be evaluated.' + ), }, { insertText: 'timestamp', label: 'timestamp', detail: 'timestamp(v instant-vector)', - documentation: - 'Returns the timestamp of each of the samples of the given vector as the number of seconds since January 1, 1970 UTC.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-timestamp', + 'Returns the timestamp of each of the samples of the given vector as the number of seconds since January 1, 1970 UTC.' + ), }, { insertText: 'vector', label: 'vector', detail: 'vector(s scalar)', - documentation: 'Returns the scalar `s` as a vector with no labels.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-vector', + 'Returns the scalar `s` as a vector with no labels.' + ), }, { insertText: 'year', label: 'year', detail: 'year(v=vector(time()) instant-vector)', - documentation: 'Returns the year for each of the given times in UTC.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-year', + 'Returns the year for each of the given times in UTC.' + ), }, { insertText: 'avg_over_time', label: 'avg_over_time', detail: 'avg_over_time(range-vector)', - documentation: 'The average value of all points in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-avg-over-time', + 'The average value of all points in the specified interval.' + ), }, { insertText: 'min_over_time', label: 'min_over_time', detail: 'min_over_time(range-vector)', - documentation: 'The minimum value of all points in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-min-over-time', + 'The minimum value of all points in the specified interval.' + ), }, { insertText: 'max_over_time', label: 'max_over_time', detail: 'max_over_time(range-vector)', - documentation: 'The maximum value of all points in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-max-over-time', + 'The maximum value of all points in the specified interval.' + ), }, { insertText: 'sum_over_time', label: 'sum_over_time', detail: 'sum_over_time(range-vector)', - documentation: 'The sum of all values in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-sum-over-time', + 'The sum of all values in the specified interval.' + ), }, { insertText: 'count_over_time', label: 'count_over_time', detail: 'count_over_time(range-vector)', - documentation: 'The count of all values in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-count-over-time', + 'The count of all values in the specified interval.' + ), }, { insertText: 'quantile_over_time', label: 'quantile_over_time', detail: 'quantile_over_time(scalar, range-vector)', - documentation: 'The φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-quantile-over-time', + 'The φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.' + ), }, { insertText: 'stddev_over_time', label: 'stddev_over_time', detail: 'stddev_over_time(range-vector)', - documentation: 'The population standard deviation of the values in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-stddev-over-time', + 'The population standard deviation of the values in the specified interval.' + ), }, { insertText: 'stdvar_over_time', label: 'stdvar_over_time', detail: 'stdvar_over_time(range-vector)', - documentation: 'The population standard variance of the values in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-stdvar-over-time', + 'The population standard variance of the values in the specified interval.' + ), }, { insertText: 'last_over_time', label: 'last_over_time', detail: 'last_over_time(range-vector)', - documentation: 'The most recent point value in specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-last-over-time', + 'The most recent point value in specified interval.' + ), }, { insertText: 'present_over_time', label: 'present_over_time', detail: 'present_over_time(range-vector)', - documentation: 'The value 1 for any series in the specified interval.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-present-over-time', + 'The value 1 for any series in the specified interval.' + ), }, { insertText: 'histogram_avg', label: 'histogram_avg', detail: 'histogram_avg(v instant-vector)', - documentation: - 'Returns the arithmetic average of observed values stored in a native histogram. Samples that are not native histograms are ignored and do not show up in the returned vector.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-avg', + 'Returns the arithmetic average of observed values stored in a native histogram. Samples that are not native histograms are ignored and do not show up in the returned vector.' + ), }, { insertText: 'histogram_count', label: 'histogram_count', detail: 'histogram_count(v instant-vector)', - documentation: 'Returns the count of observations stored in a native histogram.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-count', + 'Returns the count of observations stored in a native histogram.' + ), }, { insertText: 'histogram_sum', label: 'histogram_sum', detail: 'histogram_sum(v instant-vector)', - documentation: 'Returns the sum of observations stored in a native histogram.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-sum', + 'Returns the sum of observations stored in a native histogram.' + ), }, { insertText: 'histogram_fraction', label: 'histogram_fraction', detail: 'histogram_fraction(lower scalar, upper scalar, v instant-vector)', - documentation: 'Returns the estimated fraction of observations between the provided lower and upper values.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-fraction', + 'Returns the estimated fraction of observations between the provided lower and upper values.' + ), }, { insertText: 'histogram_stddev', label: 'histogram_stddev', detail: 'histogram_stddev(v instant-vector)', - documentation: - 'Returns the estimated standard deviation of observations in a native histogram, based on the geometric mean of the buckets where the observations lie.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-stddev', + 'Returns the estimated standard deviation of observations in a native histogram, based on the geometric mean of the buckets where the observations lie.' + ), }, { insertText: 'histogram_stdvar', label: 'histogram_stdvar', detail: 'histogram_stdvar(v instant-vector)', - documentation: 'Returns the estimated standard variance of observations in a native histogram.', + documentation: t( + 'grafana-prometheus.promql.getFunctions.documentation-histogram-stdvar', + 'Returns the estimated standard variance of observations in a native histogram.' + ), }, ]; @@ -610,7 +852,12 @@ export const promqlGrammar: Grammar = { punctuation: /[{]/, }, }, - function: new RegExp(`\\b(?:${FUNCTIONS.map((f) => f.label).join('|')})(?=\\s*\\()`, 'i'), + function: new RegExp( + `\\b(?:${getFunctions() + .map((f) => f.label) + .join('|')})(?=\\s*\\()`, + 'i' + ), 'context-range': [ { pattern: /\[[^\]]*(?=])/, // [1m] diff --git a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts index caf90017526..9286382d7eb 100644 --- a/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts +++ b/packages/grafana-prometheus/src/querybuilder/PromQueryModeller.ts @@ -1,5 +1,5 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts -import { FUNCTIONS } from '../promql'; +import { getFunctions } from '../promql'; import { getAggregationOperations } from './aggregations'; import { getOperationDefinitions } from './operations'; @@ -16,7 +16,7 @@ export class PromQueryModeller extends PromQueryModellerBase implements PromQuer super(() => { const allOperations = [...getOperationDefinitions(), ...getAggregationOperations()]; for (const op of allOperations) { - const func = FUNCTIONS.find((x) => x.insertText === op.id); + const func = getFunctions().find((x) => x.insertText === op.id); if (func) { op.documentation = func.documentation; } diff --git a/packages/grafana-prometheus/src/querybuilder/operationUtils.ts b/packages/grafana-prometheus/src/querybuilder/operationUtils.ts index 00f9bc72b0c..227621b1734 100644 --- a/packages/grafana-prometheus/src/querybuilder/operationUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/operationUtils.ts @@ -3,6 +3,7 @@ import { capitalize } from 'lodash'; import pluralize from 'pluralize'; import { SelectableValue } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { QueryBuilderLabelFilter, @@ -265,11 +266,23 @@ export function getAggregationExplainer(aggregationName: string, mode: 'by' | 'w switch (mode) { case 'by': - return `Calculates ${aggregationName} over dimensions while preserving ${labelWord} ${labels}.`; + return t( + 'grafana-prometheus.querybuilder.operation-utils.getAggregationExplainer.label-by', + 'Calculates {{aggregationName}} over dimensions while preserving {{labelWord}} {{labels}}.', + { aggregationName, labelWord, labels } + ); case 'without': - return `Calculates ${aggregationName} over the dimensions ${labels}. All other labels are preserved.`; + return t( + 'grafana-prometheus.querybuilder.operation-utils.getAggregationExplainer.label-without', + 'Calculates {{aggregationName}} over the dimensions {{labels}}. All other labels are preserved.', + { aggregationName, labels } + ); default: - return `Calculates ${aggregationName} over the dimensions.`; + return t( + 'grafana-prometheus.querybuilder.operation-utils.getAggregationExplainer.label-default', + 'Calculates {{aggregationName}} over the dimensions.', + { aggregationName } + ); } }; } diff --git a/packages/grafana-prometheus/src/resource_clients.test.ts b/packages/grafana-prometheus/src/resource_clients.test.ts index f919615b44e..4a2790227ee 100644 --- a/packages/grafana-prometheus/src/resource_clients.test.ts +++ b/packages/grafana-prometheus/src/resource_clients.test.ts @@ -836,6 +836,11 @@ describe('BaseResourceClient', () => { constructor() { super(mockRequest, mockDatasource); } + + // Expose protected method for testing + public testGetEffectiveLimit(limit?: number): number { + return this.getEffectiveLimit(limit); + } } let client: TestBaseResourceClient; @@ -845,6 +850,26 @@ describe('BaseResourceClient', () => { client = new TestBaseResourceClient(); }); + describe('getEffectiveLimit', () => { + it('should return the provided limit when a number is given', () => { + expect(client.testGetEffectiveLimit(1000)).toBe(1000); + expect(client.testGetEffectiveLimit(500)).toBe(500); + expect(client.testGetEffectiveLimit(100000)).toBe(100000); + }); + + it('should return 0 when limit is 0 (valid for unlimited)', () => { + expect(client.testGetEffectiveLimit(0)).toBe(0); + }); + + it('should return datasource seriesLimit when limit is undefined', () => { + expect(client.testGetEffectiveLimit(undefined)).toBe(DEFAULT_SERIES_LIMIT); + }); + + it('should return datasource seriesLimit when no limit is provided', () => { + expect(client.testGetEffectiveLimit()).toBe(DEFAULT_SERIES_LIMIT); + }); + }); + describe('querySeries', () => { const mockTimeRange = { from: dateTime(1681300292392), diff --git a/packages/grafana-prometheus/src/resource_clients.ts b/packages/grafana-prometheus/src/resource_clients.ts index cc59213cf47..2eb4a6b2ce0 100644 --- a/packages/grafana-prometheus/src/resource_clients.ts +++ b/packages/grafana-prometheus/src/resource_clients.ts @@ -43,8 +43,16 @@ export abstract class BaseResourceClient { this.seriesLimit = this.datasource.seriesLimit; } + /** + * Returns the effective limit to use for API requests. + * Uses the provided limit if specified, otherwise falls back to the datasource's configured series limit. + * When zero is provided, it returns zero (which means no limit in Prometheus API). + * + * @param {number} [limit] - Optional limit parameter from the API call + * @returns {number} The limit to use - either the provided limit or datasource's default series limit + */ protected getEffectiveLimit(limit?: number): number { - return limit || this.seriesLimit; + return limit ?? this.seriesLimit; } protected async requestLabels( @@ -93,10 +101,23 @@ export class LabelsApiClient extends BaseResourceClient implements ResourceApiCl this.labelKeys = await this.queryLabelKeys(timeRange); }; - public queryMetrics = async (timeRange: TimeRange): Promise<{ metrics: string[]; histogramMetrics: string[] }> => { - this.metrics = await this.queryLabelValues(timeRange, METRIC_LABEL); + /** + * Fetches all available metrics from Prometheus using the labels values endpoint for __name__. + * Also processes and identifies histogram metrics (those ending with '_bucket'). + * Results are cached and stored in the client instance for future use. + * + * @param {TimeRange} timeRange - Time range to search for metrics + * @param {number} [limit] - Optional maximum number of metrics to return, uses datasource default if not specified + * @returns {Promise<{metrics: string[], histogramMetrics: string[]}>} Object containing all metrics and filtered histogram metrics + */ + public queryMetrics = async ( + timeRange: TimeRange, + limit?: number + ): Promise<{ metrics: string[]; histogramMetrics: string[] }> => { + const effectiveLimit = this.getEffectiveLimit(limit); + this.metrics = await this.queryLabelValues(timeRange, METRIC_LABEL, undefined, effectiveLimit); this.histogramMetrics = processHistogramMetrics(this.metrics); - this._cache.setLabelValues(timeRange, undefined, DEFAULT_SERIES_LIMIT, this.metrics); + this._cache.setLabelValues(timeRange, undefined, effectiveLimit, this.metrics); return { metrics: this.metrics, histogramMetrics: this.histogramMetrics }; }; diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 3d129df19ae..f32cc4639da 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -53,11 +53,11 @@ "postpack": "mv package.json.bak package.json && rimraf ./unstable" }, "dependencies": { - "@grafana/data": "12.1.0-pre", - "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "@types/systemjs": "6.15.3", "history": "4.10.1", "lodash": "4.17.21", @@ -71,20 +71,20 @@ "@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-terser": "0.4.4", "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/history": "4.7.11", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "lodash": "4.17.21", "react": "18.3.1", "react-dom": "18.3.1", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "rollup-plugin-sourcemaps": "0.6.3", "typescript": "5.8.3" diff --git a/packages/grafana-runtime/rollup.config.ts b/packages/grafana-runtime/rollup.config.ts index 7255b34b0d5..5807c919136 100644 --- a/packages/grafana-runtime/rollup.config.ts +++ b/packages/grafana-runtime/rollup.config.ts @@ -10,10 +10,12 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-runtime')], + treeshake: false, }, { input: 'src/unstable.ts', plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-runtime')], + treeshake: false, }, ]; diff --git a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx index e235aace668..14f1a9082be 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx +++ b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx @@ -92,7 +92,11 @@ describe('Plugin Extensions / Utils', () => { // Check if the right components are selected const limitedComponents = getLimitedComponentsToRender({ props, components, limit: 3 }); const rendered = render( - <>{limitedComponents?.map((Component, index) => )} + <> + {limitedComponents?.map((Component, index) => ( + + ))} + ); expect(rendered.getByText('Test 1')).toBeInTheDocument(); @@ -127,7 +131,11 @@ describe('Plugin Extensions / Utils', () => { // Check if the right components are selected const limitedComponents = getLimitedComponentsToRender({ props, components, limit: 1 }); const rendered = render( - <>{limitedComponents?.map((Component, index) => )} + <> + {limitedComponents?.map((Component, index) => ( + + ))} + ); expect(rendered.getByText('Test 1')).toBeInTheDocument(); @@ -158,7 +166,11 @@ describe('Plugin Extensions / Utils', () => { pluginId: ['plugin-id-2', 'plugin-id-3'], }); const rendered = render( - <>{limitedComponents?.map((Component, index) => )} + <> + {limitedComponents?.map((Component, index) => ( + + ))} + ); expect(rendered.getByText('Test 2')).toBeInTheDocument(); diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 8dc2e5efe1f..9896376d716 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" @@ -38,11 +38,11 @@ "devDependencies": { "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "16.0.1", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "glob": "^11.0.0", "rimraf": "6.0.1", "rollup": "^4.22.4", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "typescript": "5.8.3" }, diff --git a/packages/grafana-schema/rollup.config.ts b/packages/grafana-schema/rollup.config.ts index e274b31df26..04928f75c47 100644 --- a/packages/grafana-schema/rollup.config.ts +++ b/packages/grafana-schema/rollup.config.ts @@ -16,6 +16,7 @@ export default [ input: entryPoint, plugins, output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-schema')], + treeshake: false, }, { input: Object.fromEntries( @@ -45,5 +46,6 @@ export default [ format: 'esm', dir: path.dirname(pkg.publishConfig.module), }, + treeshake: false, }, ]; diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index 7efc5e3ff60..b98c83c9525 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -882,6 +882,16 @@ export type TimeZoneUtc = 'utc'; */ export type TimeZoneBrowser = 'browser'; +/** + * Options for time comparison + */ +export interface TimeCompareOptions { + /** + * Enable time comparison control + */ + timeCompare?: boolean; +} + /** * Optional formats for the template variable replace functions * See also https://grafana.com/docs/grafana/latest/dashboards/variables/variable-syntax/#advanced-variable-format-options diff --git a/packages/grafana-schema/src/common/timecompare.cue b/packages/grafana-schema/src/common/timecompare.cue new file mode 100644 index 00000000000..b85287c9301 --- /dev/null +++ b/packages/grafana-schema/src/common/timecompare.cue @@ -0,0 +1,7 @@ +package common + +// Options for time comparison +TimeCompareOptions: { + // Enable time comparison control + timeCompare?: bool +} @cuetsy(kind="interface") \ No newline at end of file diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index 6d79fbeceb8..63bb4339949 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index 1bc5ff0f497..59924a010ae 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index 4345b7fae05..c086da202b8 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index ee96c69c0a9..ea817787d65 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export enum VizDisplayMode { Candles = 'candles', diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 84c0c84408d..983129db72b 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index 9f339bc8cc4..f0e4394451d 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface MetricStat { /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index 328429a5829..1348c30dbad 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index 99b9e516dba..d8db9ef7edf 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index 6967fd64ccb..811836dff43 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index 7cac1eab340..dfb50c2c981 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index abe40c63645..36aa14da169 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index 14a718cf6d9..0a085d8ec6e 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index b87a6c3897f..b0a2a2b5233 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; /** * Controls the color mode of the heatmap diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index 819bb518581..59b82823d78 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index 956bff430f7..da04269f0b3 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { controlsStorageKey?: string; @@ -24,6 +24,7 @@ export interface Options { logLineMenuCustomItems?: unknown; logRowMenuIconsAfter?: unknown; logRowMenuIconsBefore?: unknown; + noInteractions?: boolean; /** * TODO: figure out how to define callbacks */ diff --git a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts index 13e0ad8c692..2b64bba333e 100644 --- a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { controlsStorageKey?: string; diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index c86b8b987f7..eb0b74331d4 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index 10f74d337b8..a5e4657464a 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index 5061e824e18..c5f420a0b11 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index 3f290835b85..8296f089a14 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index 40af7a986a7..58b487eb4d3 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index d0cc207400b..0ddf8a30c7a 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index 2529b11f635..fe36790fe55 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index b97546048d7..4fbda90032b 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index b34b0b7e301..30349a9ff9c 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,11 +10,12 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; orientation?: common.VizOrientation; + timeCompare?: common.TimeCompareOptions; tooltip: common.VizTooltipOptions; } diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index 9d3946018df..aa3b48c20ab 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 35b7fdfd4e5..13516a37619 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.1.0-pre"; +export const pluginVersion = "12.2.0-pre"; export enum PointShape { Circle = 'circle', diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 2068a48f9de..574d940a658 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -16,12 +16,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/i18n": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/i18n": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "@react-awesome-query-builder/ui": "6.6.15", "immutable": "5.1.3", "lodash": "4.17.21", @@ -39,11 +39,11 @@ "@grafana/tsconfig": "^2.0.0", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "^6.1.2", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-virtualized-auto-sizer": "1.0.8", diff --git a/packages/grafana-sql/src/locales/de-DE/grafana-sql.json b/packages/grafana-sql/src/locales/de-DE/grafana-sql.json index 5f921f12dba..152cc9af15b 100644 --- a/packages/grafana-sql/src/locales/de-DE/grafana-sql.json +++ b/packages/grafana-sql/src/locales/de-DE/grafana-sql.json @@ -2,26 +2,26 @@ "grafana-sql": { "components": { "confirm-modal": { - "builder-mode": "", + "builder-mode": "Im Builder-Modus werden keine am Code vorgenommenen Änderungen angezeigt. Der Abfrage-Builder zeigt Ihre letzten Änderungen im Builder-Modus an.", "cancel": "Abbrechen", - "clipboard": "", - "copy-code-and-switch": "", - "discard-code-and-switch": "", + "clipboard": "Möchten Sie Ihren Code in die Zwischenablage kopieren?", + "copy-code-and-switch": "Code kopieren und wechseln", + "discard-code-and-switch": "Code verwerfen und wechseln", "warning": "Warnung" }, "connection-limits": { - "auto-max-idle": "", - "content-auto-max-idle": "", - "content-max-idle": "", - "content-max-lifetime": "", - "content-max-open": "", - "max-idle": "", - "max-lifetime": "", - "max-open": "", - "title-connection-limits": "" + "auto-max-idle": "Auto. max. inaktiv", + "content-auto-max-idle": "Wenn dies aktiviert ist, wird die Anzahl der <1>Maximalen inaktiven Verbindungen automatisch auf den gleichen Wert der<3> Maximalen offenen Verbindungen festgelegt. Wenn die Anzahl der maximalen offenen Verbindungen nicht festgelegt ist, wird sie auf den Standardwert ({{defaultMaxIdle}}) eingestellt.", + "content-max-idle": "Die maximale Anzahl der Verbindungen im Pool der inaktiven Verbindungen. Wenn <1>Maximale offene Verbindungen größer ist als 0, aber kleiner als die Anzahl der <3>Maximalen inaktiven Verbindungen, werden die <5>Maximalen inaktiven Verbindungen verringert, um der Grenze für die Anzahl der <8>Maximalen offenen Verbindungen zu entsprechen. Wenn der Wert auf 0 eingestellt ist, werden keine inaktiven Verbindungen beibehalten.", + "content-max-lifetime": "Die maximale Zeitdauer der Wiederverwendung einer Verbindung in Sekunden. Bei einer Einstellung auf 0 werden Verbindungen immer wiederverwendet.", + "content-max-open": "Die maximale Anzahl offener Verbindungen zur Datenbank. Wenn <1>Maximale inaktive Verbindungen größer als 0 ist und die Anzahl der <3>Maximalen offenen Verbindungen kleiner als die Anzahl der <5>Maximalen inaktiven Verbindungen ist, werden die <7>Maximalen inaktiven Verbindungen bis zur Grenze für <9>Maximale offene Verbindungen verringert. Wenn 0 festgelegt ist, gibt es keine Begrenzung der Anzahl offener Verbindungen.", + "max-idle": "Max. inaktiv", + "max-lifetime": "Max. Lebensdauer", + "max-open": "Max. offen", + "title-connection-limits": "Verbindungsbegrenzungen" }, "dataset-selector": { - "aria-label-dataset-selector": "" + "aria-label-dataset-selector": "Datensatz-Selektor" }, "error-boundary": { "fall-back": { @@ -31,21 +31,21 @@ "get-custom-operators": { "custom-operators": { "label": { - "macros": "" + "macros": "Macros" } } }, "make-render-column": { "render-column": { "aria-label-group-by": "Gruppieren nach", - "title-remove-group-by-column": "" + "title-remove-group-by-column": "Nach Spalte gruppieren entfernen" } }, "order-by-row": { - "aria-label-order-by": "", + "aria-label-order-by": "Sortieren nach", "label-limit": "Limit", - "label-offset": "", - "label-order-by": "" + "label-offset": "Offset", + "label-order-by": "Sortieren nach" }, "preview": { "label-element": { @@ -54,100 +54,100 @@ } }, "query-header": { - "content-invalid-query": "", + "content-invalid-query": "Ihre Abfrage ist ungültig. Weitere Informationen finden Sie unten. <1>Sie können diese Abfrage aber trotzdem ausführen.", "editor-modes": { "label-builder": "", "label-code": "" }, - "label-dataset": "", + "label-dataset": "Datensatz", "label-filter": "Filter", "label-format": "Format", "label-group": "Gruppe", - "label-order": "", + "label-order": "Reihenfolge", "label-preview": "Vorschau", "label-table": "Tabelle", - "placeholder-select-format": "", + "placeholder-select-format": "Format auswählen", "run-query": "Abfrage ausführen" }, "query-toolbox": { - "content-hit-ctrlcmdreturn-to-run-query": "", - "tooltip-collapse": "", - "tooltip-expand": "", - "tooltip-format-query": "" + "content-hit-ctrlcmdreturn-to-run-query": "Drücken Sie STRG/CMD+Eingabe, um die Abfrage auszuführen", + "tooltip-collapse": "Editor einklappen", + "tooltip-expand": "Editor ausklappen", + "tooltip-format-query": "Formatabfrage" }, "query-validator": { - "query-will-process": "", - "validating-query": "" + "query-will-process": "<0> Diese Abfrage verarbeitet <2>{{bytes}}, wenn sie ausgeführt wird.", + "validating-query": "Abfrage wird validiert …" }, "raw-editor": { "render-placeholder": { - "editing-in-expanded-code-editor": "" + "editing-in-expanded-code-editor": "Bearbeitung im ausgeklappten Code-Editor" }, - "title-query-num": "" + "title-query-num": "Abfrage {{queryNum}}" }, "select-column": { "label-column": "Spalte" }, "select-custom-function-parameters": { - "aria-label-parameter": "", + "aria-label-parameter": "Parameter {{index}} für Spalte {{columnIndex}}", "render-parameters": { "params": { - "title-remove-parameter": "" + "title-remove-parameter": "Parameter entfernen" } }, - "title-add-parameter": "" + "title-add-parameter": "Parameter hinzufügen" }, "select-row": { "aggregate-options": { "options": { "label": { - "aggregations": "", - "macros": "" + "aggregations": "Aggregationen", + "macros": "Macros" } } }, "label": { - "time": "", + "time": "Zeit", "value": "Wert" }, "label-alias": "Alias", - "label-data-operations": "", - "title-add-column": "", - "title-remove-column": "" + "label-data-operations": "Datenoperationen", + "title-add-column": "Spalte hinzufügen", + "title-remove-column": "Spalte entfernen" }, "settings": { - "aria-label-conjunction": "", + "aria-label-conjunction": "Konjunktion", "aria-label-field": "Feld", "aria-label-operator": "Bediener", - "title-button-filter": "" + "title-button-filter": "{{ buttonLabel }} Filter" }, "sql-query-editor-lazy": { "text-loading-editor": "Editor wird geladen" }, "table-selector": { - "aria-label-table-selector": "", - "placeholder-loading": "", - "placeholder-select-table": "" + "aria-label-table-selector": "Tabellenselektor", + "placeholder-loading": "Tabellen werden geladen", + "placeholder-select-table": "Tabelle auswählen" }, "tlssecrets-config": { - "content-tlsssl-client-certificate": "", - "content-tlsssl-client-key": "", - "content-tlsssl-root-certificate": "", - "tlsssl-client-certificate": "", - "tlsssl-client-key": "", + "content-tlsssl-client-certificate": "Stellen Sie zur Authentifizierung mit einem TLS/SSL-Client-Zertifikat bitte hier das Client-Zertifikat bereit.", + "content-tlsssl-client-key": "Stellen Sie zur Authentifizierung mit einem Client-TLS/SSL-Zertifikat bitte hier den Schlüssel bereit.", + "content-tlsssl-root-certificate": "Wenn der ausgewählte TLS/SSL-Modus ein Server-Root-Zertifikat erfordert, stellen Sie es bitte hier bereit", + "tlsssl-client-certificate": "TLS/SSL-Client-Zertifikat", + "tlsssl-client-key": "TLS/SSL-Clientschlüssel", "tlsssl-root-certificate": "TLS/SSL-Root-Zertifikat" }, "visual-editor": { - "label-filter-by-column-value": "", - "label-group-by-column": "" + "label-filter-by-column-value": "Nach Spaltenwert filtern", + "label-group-by-column": "Nach Spalte gruppieren" }, "widgets": { - "aria-label-macros-value-selector": "" + "aria-label-macros-value-selector": "Macros-Wert-Selektor" } }, "utils": { "get-columns-width-indices": { - "label-selected-columns": "" + "label-selected-columns": "Ausgewählte Spalten" } } } diff --git a/packages/grafana-sql/src/locales/hu-HU/grafana-sql.json b/packages/grafana-sql/src/locales/hu-HU/grafana-sql.json index 0c1803084cb..bb168d14c87 100644 --- a/packages/grafana-sql/src/locales/hu-HU/grafana-sql.json +++ b/packages/grafana-sql/src/locales/hu-HU/grafana-sql.json @@ -2,26 +2,26 @@ "grafana-sql": { "components": { "confirm-modal": { - "builder-mode": "", + "builder-mode": "Az építő mód nem jeleníti meg a kódban végzett módosításokat. A lekérdezésépítő megjeleníti az utolsó módosításokat, amelyeket az építő módban végzett.", "cancel": "Mégse", - "clipboard": "", - "copy-code-and-switch": "", - "discard-code-and-switch": "", + "clipboard": "Szeretné a kódot a vágólapra másolni?", + "copy-code-and-switch": "Kód másolása és váltás", + "discard-code-and-switch": "Kód elvetése és váltás", "warning": "Figyelmeztetés" }, "connection-limits": { - "auto-max-idle": "", - "content-auto-max-idle": "", - "content-max-idle": "", - "content-max-lifetime": "", - "content-max-open": "", - "max-idle": "", - "max-lifetime": "", - "max-open": "", - "title-connection-limits": "" + "auto-max-idle": "Inaktív automatikus maximális száma", + "content-auto-max-idle": "Ha engedélyezve van, automatikusan beállítja az <1>Inaktív kapcsolatok maximális száma értéket a <3>Megnyitott kapcsolatok maximális száma értékére. Ha a nyitott kapcsolatok maximális száma nincs beállítva, akkor az alapértelmezett értékre lesz beállítva ({{defaultMaxIdle}}).", + "content-max-idle": "Az inaktív kapcsolatok készletében lévő kapcsolatok maximális száma. Ha a <1>Megnyitott kapcsolatok maximális száma nagyobb, mint 0, de kisebb, mint az <3>Inaktív kapcsolatok maximális száma, akkor az <5>Inaktív kapcsolatok maximális száma csökken, hogy megfeleljen a <8>Megnyitott kapcsolatok maximális száma határértékének. Ha 0-ra van állítva, akkor egyetlen inaktív kapcsolat sem marad meg.", + "content-max-lifetime": "A csatlakozás újrafelhasználásának maximális időtartama másodpercben. Ha 0-ra van állítva, a kapcsolatok újrafelhasználása végtelen.", + "content-max-open": "Az adatbázishoz irányuló nyitott kapcsolatok maximális száma. Ha az <1>Inaktív kapcsolatok maximális száma nagyobb, mint 0, és a <3>Megnyitott kapcsolatok maximális száma kisebb, mint az <5>Inaktív kapcsolatok maximális száma, akkor az <7>Inaktív kapcsolatok maximális száma csökken, hogy megfeleljen a <9>Megnyitott kapcsolatok maximális száma határértékének. Ha 0-ra van állítva, akkor nincs korlátozva a nyitott kapcsolatok száma.", + "max-idle": "Max. inaktív", + "max-lifetime": "Max. élettartam", + "max-open": "Max. nyitott", + "title-connection-limits": "Kapcsolati korlátozások" }, "dataset-selector": { - "aria-label-dataset-selector": "" + "aria-label-dataset-selector": "Adatkészlet-választó" }, "error-boundary": { "fall-back": { @@ -31,21 +31,21 @@ "get-custom-operators": { "custom-operators": { "label": { - "macros": "" + "macros": "Makrók" } } }, "make-render-column": { "render-column": { "aria-label-group-by": "Csoportosítási szempont", - "title-remove-group-by-column": "" + "title-remove-group-by-column": "Csoport eltávolítása oszlop szerint" } }, "order-by-row": { - "aria-label-order-by": "", + "aria-label-order-by": "Rendezési szempont", "label-limit": "Korlát", - "label-offset": "", - "label-order-by": "" + "label-offset": "Eltolás", + "label-order-by": "Rendezési szempont" }, "preview": { "label-element": { @@ -54,100 +54,100 @@ } }, "query-header": { - "content-invalid-query": "", + "content-invalid-query": "A lekérdezés érvénytelen. A részleteket tekintse meg alább. <1>Ezt a lekérdezést azonban továbbra is futtathatja.", "editor-modes": { "label-builder": "", "label-code": "" }, - "label-dataset": "", + "label-dataset": "Adatkészlet", "label-filter": "Szűrő", "label-format": "Formátum", "label-group": "Csoport", - "label-order": "", + "label-order": "Sorrend", "label-preview": "Előnézet", "label-table": "Táblázat", - "placeholder-select-format": "", + "placeholder-select-format": "Formátum kiválasztása", "run-query": "Lekérdezés" }, "query-toolbox": { - "content-hit-ctrlcmdreturn-to-run-query": "", - "tooltip-collapse": "", - "tooltip-expand": "", - "tooltip-format-query": "" + "content-hit-ctrlcmdreturn-to-run-query": "A lekérdezés futtatásához nyomja le a CTRL/CMD+Enter billentyűkombinációt", + "tooltip-collapse": "Szerkesztő összecsukása", + "tooltip-expand": "Szerkesztő kibontása", + "tooltip-format-query": "Lekérdezés formázása" }, "query-validator": { - "query-will-process": "", - "validating-query": "" + "query-will-process": "<0> Ez a lekérdezés <2>{{bytes}} feldolgozását végzi futtatáskor.", + "validating-query": "Lekérdezés érvényesítése…" }, "raw-editor": { "render-placeholder": { - "editing-in-expanded-code-editor": "" + "editing-in-expanded-code-editor": "Szerkesztés kibontott kódszerkesztőben" }, - "title-query-num": "" + "title-query-num": "{{queryNum}}. lekérdezés" }, "select-column": { "label-column": "Oszlop" }, "select-custom-function-parameters": { - "aria-label-parameter": "", + "aria-label-parameter": "{{columnIndex}}. oszlop {{index}}. paramétere", "render-parameters": { "params": { - "title-remove-parameter": "" + "title-remove-parameter": "Paraméter eltávolítása" } }, - "title-add-parameter": "" + "title-add-parameter": "Paraméter hozzáadása" }, "select-row": { "aggregate-options": { "options": { "label": { - "aggregations": "", - "macros": "" + "aggregations": "Összegzések", + "macros": "Makrók" } } }, "label": { - "time": "", + "time": "idő", "value": "érték" }, "label-alias": "Alias", - "label-data-operations": "", - "title-add-column": "", - "title-remove-column": "" + "label-data-operations": "Adatműveletek", + "title-add-column": "Oszlop hozzáadása", + "title-remove-column": "Oszlop eltávolítása" }, "settings": { - "aria-label-conjunction": "", + "aria-label-conjunction": "Összekötés", "aria-label-field": "Mező", "aria-label-operator": "Műveleti jel", - "title-button-filter": "" + "title-button-filter": "{{ buttonLabel }} szűrő" }, "sql-query-editor-lazy": { "text-loading-editor": "Szerkesztő betöltése" }, "table-selector": { - "aria-label-table-selector": "", - "placeholder-loading": "", - "placeholder-select-table": "" + "aria-label-table-selector": "Táblázatválasztó", + "placeholder-loading": "Táblázatok betöltése", + "placeholder-select-table": "Táblázat kiválasztása" }, "tlssecrets-config": { - "content-tlsssl-client-certificate": "", - "content-tlsssl-client-key": "", - "content-tlsssl-root-certificate": "", - "tlsssl-client-certificate": "", - "tlsssl-client-key": "", + "content-tlsssl-client-certificate": "A TLS-/SSL-klienstanúsítvánnyal történő hitelesítéshez adja meg itt a klienstanúsítványt.", + "content-tlsssl-client-key": "A TLS-/SSL-klienstanúsítvánnyal történő hitelesítéshez adja meg itt a kulcsot.", + "content-tlsssl-root-certificate": "Ha a kiválasztott TLS-/SSL-módhoz kiszolgáló-gyökértanúsítvány szükséges, adja meg itt", + "tlsssl-client-certificate": "TLS-/SSL-klienstanúsítvány", + "tlsssl-client-key": "TLS-/SSL-klienskulcs", "tlsssl-root-certificate": "TLS-/SSL-gyökértanúsítvány" }, "visual-editor": { - "label-filter-by-column-value": "", - "label-group-by-column": "" + "label-filter-by-column-value": "Szűrés oszlopérték szerint", + "label-group-by-column": "Csoportosítás oszlop szerint" }, "widgets": { - "aria-label-macros-value-selector": "" + "aria-label-macros-value-selector": "Makróérték-választó" } }, "utils": { "get-columns-width-indices": { - "label-selected-columns": "" + "label-selected-columns": "Kijelölt oszlopok" } } } diff --git a/packages/grafana-sql/src/locales/id-ID/grafana-sql.json b/packages/grafana-sql/src/locales/id-ID/grafana-sql.json index e6c977bca22..e4344f3ee68 100644 --- a/packages/grafana-sql/src/locales/id-ID/grafana-sql.json +++ b/packages/grafana-sql/src/locales/id-ID/grafana-sql.json @@ -2,26 +2,26 @@ "grafana-sql": { "components": { "confirm-modal": { - "builder-mode": "", + "builder-mode": "Mode pembuat tidak menampilkan perubahan yang dibuat dalam kode. Pembuat kueri akan menampilkan perubahan terakhir yang Anda buat dalam mode pembuat.", "cancel": "Batalkan", - "clipboard": "", - "copy-code-and-switch": "", - "discard-code-and-switch": "", + "clipboard": "Ingin menyalin kode Anda ke papan klip?", + "copy-code-and-switch": "Salin kode dan beralih", + "discard-code-and-switch": "Buang kode dan beralih", "warning": "PERINGATAN" }, "connection-limits": { - "auto-max-idle": "", - "content-auto-max-idle": "", - "content-max-idle": "", - "content-max-lifetime": "", - "content-max-open": "", - "max-idle": "", - "max-lifetime": "", - "max-open": "", - "title-connection-limits": "" + "auto-max-idle": "Nonaktif maksimum otomatis", + "content-auto-max-idle": "Jika diaktifkan, secara otomatis atur jumlah <1>Koneksi nonaktif maksimum ke nilai yang sama dengan<3> Koneksi terbuka maksimum. Jika jumlah maksimum koneksi terbuka tidak diatur, jumlah tersebut akan diatur ke default ({{defaultMaxIdle}}).", + "content-max-idle": "Jumlah maksimum koneksi dalam pool koneksi nonaktif. Jika <1>Koneksi terbuka maksimum lebih besar dari 0 tetapi kurang dari <3>Koneksi nonaktif maksimum, maka <5>Koneksi nonaktif maksimum akan dikurangi agar sesuai dengan batas <8>Koneksi terbuka maksimum. Jika diatur ke 0, tidak ada koneksi nonaktif yang dipertahankan.", + "content-max-lifetime": "Jumlah waktu maksimum dalam detik untuk menggunakan koneksi lagi. Jika diatur ke 0, koneksi digunakan kembali terus-menerus tanpa batas waktu.", + "content-max-open": "Jumlah koneksi terbuka maksimum ke basis data. Jika <1>Koneksi nonaktif maksimum lebih besar dari 0 dan <3>Koneksi terbuka maksimum kurang dari <5>Koneksi nonaktif maksimum, maka <7>Koneksi nonaktif maksimum akan dikurangi agar sesuai dengan batas <9>Koneksi terbuka maksimum. Jika diatur ke 0, tidak ada batasan jumlah koneksi terbuka.", + "max-idle": "Nonaktif maks", + "max-lifetime": "Periode maks", + "max-open": "Terbuka maks", + "title-connection-limits": "Batas koneksi" }, "dataset-selector": { - "aria-label-dataset-selector": "" + "aria-label-dataset-selector": "Selektor kumpulan data" }, "error-boundary": { "fall-back": { @@ -31,21 +31,21 @@ "get-custom-operators": { "custom-operators": { "label": { - "macros": "" + "macros": "Makro" } } }, "make-render-column": { "render-column": { "aria-label-group-by": "Kelompokkan berdasarkan", - "title-remove-group-by-column": "" + "title-remove-group-by-column": "Hapus grup berdasarkan kolom" } }, "order-by-row": { - "aria-label-order-by": "", + "aria-label-order-by": "Urutkan Berdasarkan", "label-limit": "Batas", - "label-offset": "", - "label-order-by": "" + "label-offset": "Offset", + "label-order-by": "Urutkan Berdasarkan" }, "preview": { "label-element": { @@ -54,100 +54,100 @@ } }, "query-header": { - "content-invalid-query": "", + "content-invalid-query": "Kueri Anda tidak valid. Periksa di bawah ini untuk detailnya. <1>Namun, Anda masih dapat menjalankan kueri ini.", "editor-modes": { "label-builder": "", "label-code": "" }, - "label-dataset": "", + "label-dataset": "Kumpulan data", "label-filter": "Filter", "label-format": "Format", "label-group": "Grup", - "label-order": "", + "label-order": "Pesanan", "label-preview": "Pratinjau", "label-table": "Tabel", - "placeholder-select-format": "", + "placeholder-select-format": "Pilih format", "run-query": "Jalankan kueri" }, "query-toolbox": { - "content-hit-ctrlcmdreturn-to-run-query": "", - "tooltip-collapse": "", - "tooltip-expand": "", - "tooltip-format-query": "" + "content-hit-ctrlcmdreturn-to-run-query": "Tekan CTRL/CMD+Return untuk menjalankan kueri", + "tooltip-collapse": "Ciutkan editor", + "tooltip-expand": "Perluas editor", + "tooltip-format-query": "Format kueri" }, "query-validator": { - "query-will-process": "", - "validating-query": "" + "query-will-process": "<0> Kueri ini akan memproses <2>{{bytes}} saat dijalankan.", + "validating-query": "Memvalidasi kueri..." }, "raw-editor": { "render-placeholder": { - "editing-in-expanded-code-editor": "" + "editing-in-expanded-code-editor": "Mengedit di editor kode yang diperluas" }, - "title-query-num": "" + "title-query-num": "Kueri {{queryNum}}" }, "select-column": { "label-column": "Kolom" }, "select-custom-function-parameters": { - "aria-label-parameter": "", + "aria-label-parameter": "Parameter {{index}} untuk kolom {{columnIndex}}", "render-parameters": { "params": { - "title-remove-parameter": "" + "title-remove-parameter": "Hapus parameter" } }, - "title-add-parameter": "" + "title-add-parameter": "Tambahkan parameter" }, "select-row": { "aggregate-options": { "options": { "label": { - "aggregations": "", - "macros": "" + "aggregations": "Agregasi", + "macros": "Makro" } } }, "label": { - "time": "", + "time": "waktu", "value": "nilai" }, "label-alias": "Alias", - "label-data-operations": "", - "title-add-column": "", - "title-remove-column": "" + "label-data-operations": "Operasi data", + "title-add-column": "Tambah kolom", + "title-remove-column": "Hapus kolom" }, "settings": { - "aria-label-conjunction": "", + "aria-label-conjunction": "Kata Penghubung", "aria-label-field": "Bidang", "aria-label-operator": "Operator", - "title-button-filter": "" + "title-button-filter": "Filter {{ buttonLabel }}" }, "sql-query-editor-lazy": { "text-loading-editor": "Memuat editor" }, "table-selector": { - "aria-label-table-selector": "", - "placeholder-loading": "", - "placeholder-select-table": "" + "aria-label-table-selector": "Selektor tabel", + "placeholder-loading": "Memuat tabel", + "placeholder-select-table": "Pilih tabel" }, "tlssecrets-config": { - "content-tlsssl-client-certificate": "", - "content-tlsssl-client-key": "", - "content-tlsssl-root-certificate": "", - "tlsssl-client-certificate": "", - "tlsssl-client-key": "", + "content-tlsssl-client-certificate": "Untuk mengautentikasi dengan sertifikat klien TLS/SSL, berikan sertifikat klien di sini.", + "content-tlsssl-client-key": "Untuk mengautentikasi dengan sertifikat TLS/SSL klien, berikan kunci di sini.", + "content-tlsssl-root-certificate": "Jika mode TLS/SSL yang dipilih memerlukan sertifikat root server, berikan di sini", + "tlsssl-client-certificate": "Sertifikat Klien TLS/SSL", + "tlsssl-client-key": "Kunci Klien TLS", "tlsssl-root-certificate": "Sertifikat Root TLS/SSL" }, "visual-editor": { - "label-filter-by-column-value": "", - "label-group-by-column": "" + "label-filter-by-column-value": "Filter berdasarkan nilai kolom", + "label-group-by-column": "Kelompokkan berdasarkan kolom" }, "widgets": { - "aria-label-macros-value-selector": "" + "aria-label-macros-value-selector": "Selektor nilai makro" } }, "utils": { "get-columns-width-indices": { - "label-selected-columns": "" + "label-selected-columns": "Kolom yang dipilih" } } } diff --git a/packages/grafana-sql/src/locales/ja-JP/grafana-sql.json b/packages/grafana-sql/src/locales/ja-JP/grafana-sql.json index 4340b874c6e..8c6668317e3 100644 --- a/packages/grafana-sql/src/locales/ja-JP/grafana-sql.json +++ b/packages/grafana-sql/src/locales/ja-JP/grafana-sql.json @@ -2,26 +2,26 @@ "grafana-sql": { "components": { "confirm-modal": { - "builder-mode": "", + "builder-mode": "ビルダーモードではコードに加えられた変更は反映されません。クエリビルダーはビルダーモードで加えられた最後の変更を表示します。", "cancel": "キャンセル", - "clipboard": "", - "copy-code-and-switch": "", - "discard-code-and-switch": "", + "clipboard": "コードをクリップボードにコピーしますか?", + "copy-code-and-switch": "コードをコピーして切り替え", + "discard-code-and-switch": "コードを破棄して切り替え", "warning": "警告" }, "connection-limits": { - "auto-max-idle": "", - "content-auto-max-idle": "", - "content-max-idle": "", - "content-max-lifetime": "", - "content-max-open": "", - "max-idle": "", - "max-lifetime": "", - "max-open": "", - "title-connection-limits": "" + "auto-max-idle": "自動最大アイドル", + "content-auto-max-idle": "有効にすると、<1>最大アイドル接続数が<3>最大オープン接続数と同じ値に自動設定されます。最大オープン接続数が設定されていない場合、デフォルト({{defaultMaxIdle}})に設定されます。", + "content-max-idle": "アイドル接続プールの最大接続数。<1>最大オープン接続数が0より大きく<3>最大アイドル接続数より小さい場合、<5>最大アイドル接続数は<8>最大オープン接続数の制限に合わせて削減されます。0に設定すると、アイドル接続は保持されません。", + "content-max-lifetime": "接続を再利用できる最大時間(秒)。0に設定すると、接続は永続的に再利用されます。", + "content-max-open": "データベースへの最大オープン接続数。<1>最大アイドル接続数が0より大きく<3>最大オープン接続数が<5>最大アイドル接続数より小さい場合、<7>最大アイドル接続数は<9>最大オープン接続数の制限に合わせて削減されます。0に設定すると、オープン接続数に制限はありません。", + "max-idle": "最大アイドル", + "max-lifetime": "最大有効期間", + "max-open": "最大オープン", + "title-connection-limits": "接続制限" }, "dataset-selector": { - "aria-label-dataset-selector": "" + "aria-label-dataset-selector": "データセットセレクター" }, "error-boundary": { "fall-back": { @@ -31,21 +31,21 @@ "get-custom-operators": { "custom-operators": { "label": { - "macros": "" + "macros": "マクロ" } } }, "make-render-column": { "render-column": { "aria-label-group-by": "グループ化", - "title-remove-group-by-column": "" + "title-remove-group-by-column": "グループ化列を削除" } }, "order-by-row": { - "aria-label-order-by": "", + "aria-label-order-by": "並び順", "label-limit": "制限", - "label-offset": "", - "label-order-by": "" + "label-offset": "オフセット", + "label-order-by": "並び順" }, "preview": { "label-element": { @@ -54,100 +54,100 @@ } }, "query-header": { - "content-invalid-query": "", + "content-invalid-query": "クエリが無効です。詳細は下記をご確認ください。<1>ただし、このクエリは引き続き実行できます。", "editor-modes": { "label-builder": "", "label-code": "" }, - "label-dataset": "", + "label-dataset": "データセット", "label-filter": "フィルタリング", "label-format": "形式", "label-group": "グループ", - "label-order": "", + "label-order": "順番", "label-preview": "プレビュー", "label-table": "テーブル", - "placeholder-select-format": "", + "placeholder-select-format": "フォーマットを選択", "run-query": "クエリの実行" }, "query-toolbox": { - "content-hit-ctrlcmdreturn-to-run-query": "", - "tooltip-collapse": "", - "tooltip-expand": "", - "tooltip-format-query": "" + "content-hit-ctrlcmdreturn-to-run-query": "CTRL/CMD+Returnでクエリを実行", + "tooltip-collapse": "エディターを折りたたむ", + "tooltip-expand": "エディターを展開", + "tooltip-format-query": "クエリをフォーマット" }, "query-validator": { - "query-will-process": "", - "validating-query": "" + "query-will-process": "<0>このクエリは実行時に<2>{{bytes}}を処理します。", + "validating-query": "クエリを検証中..." }, "raw-editor": { "render-placeholder": { - "editing-in-expanded-code-editor": "" + "editing-in-expanded-code-editor": "展開されたコードエディターで編集中" }, - "title-query-num": "" + "title-query-num": "{{queryNum}}個のクエリ" }, "select-column": { "label-column": "列" }, "select-custom-function-parameters": { - "aria-label-parameter": "", + "aria-label-parameter": "列{{columnIndex}}のパラメーター{{index}}", "render-parameters": { "params": { - "title-remove-parameter": "" + "title-remove-parameter": "パラメーターを削除" } }, - "title-add-parameter": "" + "title-add-parameter": "パラメーターを追加" }, "select-row": { "aggregate-options": { "options": { "label": { - "aggregations": "", - "macros": "" + "aggregations": "集計", + "macros": "マクロ" } } }, "label": { - "time": "", + "time": "時間", "value": "値" }, "label-alias": "エイリアス", - "label-data-operations": "", - "title-add-column": "", - "title-remove-column": "" + "label-data-operations": "データ操作", + "title-add-column": "列を追加", + "title-remove-column": "列を削除" }, "settings": { - "aria-label-conjunction": "", + "aria-label-conjunction": "結合", "aria-label-field": "フィールド", "aria-label-operator": "オペレーター", - "title-button-filter": "" + "title-button-filter": "{{ buttonLabel }}フィルター" }, "sql-query-editor-lazy": { "text-loading-editor": "エディタを読み込んでいます" }, "table-selector": { - "aria-label-table-selector": "", - "placeholder-loading": "", - "placeholder-select-table": "" + "aria-label-table-selector": "テーブルセレクター", + "placeholder-loading": "テーブルを読み込み中...", + "placeholder-select-table": "テーブルを選択" }, "tlssecrets-config": { - "content-tlsssl-client-certificate": "", - "content-tlsssl-client-key": "", - "content-tlsssl-root-certificate": "", - "tlsssl-client-certificate": "", - "tlsssl-client-key": "", + "content-tlsssl-client-certificate": "TLS/SSLクライアント証明書で認証するには、ここにクライアント証明書を入力します。", + "content-tlsssl-client-key": "クライアントTLS/SSL証明書で認証するには、ここにキーを入力します。", + "content-tlsssl-root-certificate": "選択したTLS/SSLモードでサーバールート証明書が必要な場合は、ここに入力します", + "tlsssl-client-certificate": "TLS/SSLクライアント証明書", + "tlsssl-client-key": "TLS/SSLクライアントキー", "tlsssl-root-certificate": "TLS/SSLルート証明書" }, "visual-editor": { - "label-filter-by-column-value": "", - "label-group-by-column": "" + "label-filter-by-column-value": "列の値でフィルタリング", + "label-group-by-column": "列でグループ化" }, "widgets": { - "aria-label-macros-value-selector": "" + "aria-label-macros-value-selector": "マクロ値セレクター" } }, "utils": { "get-columns-width-indices": { - "label-selected-columns": "" + "label-selected-columns": "選択された列" } } } diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index 6df13ad831e..7f58c62ac0d 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/test-utils", - "version": "12.0.0-pre", + "version": "12.2.0-pre", "private": true, "description": "Grafana test utils & Mock API", "keywords": [ @@ -50,7 +50,7 @@ }, "devDependencies": { "@grafana/tsconfig": "^2.0.0", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "typescript": "5.8.3" } } diff --git a/packages/grafana-ui/.storybook/copyAssets.ts b/packages/grafana-ui/.storybook/copyAssets.ts index e49cd32b5d0..198953b30e3 100644 --- a/packages/grafana-ui/.storybook/copyAssets.ts +++ b/packages/grafana-ui/.storybook/copyAssets.ts @@ -4,7 +4,7 @@ // when publishing new storybook. // Note: Storybook has a static copying feature but it copies entire directories which can contain thousands of icons. -import { copySync, emptyDirSync, lstatSync } from 'fs-extra'; +import { existsSync, copySync, lstatSync } from 'fs-extra'; import { resolve } from 'node:path'; // avoid importing from @grafana/data to prevent error: window is not defined @@ -52,16 +52,13 @@ export function copyAssetsSync() { }, ]; - const staticDir = resolve(__dirname, 'static', 'public'); - - emptyDirSync(staticDir); - for (const asset of assets) { const fromPath = resolve(__dirname, asset.from); const toPath = resolve(__dirname, asset.to); - - copySync(fromPath, toPath, { - filter: (src) => !lstatSync(src).isSymbolicLink(), - }); + if (!existsSync(toPath)) { + copySync(fromPath, toPath, { + filter: (src) => !lstatSync(src).isSymbolicLink(), + }); + } } } diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index 803d45315e3..39542016eab 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -64,6 +64,7 @@ const preview: Preview = { docs: { container: ThemedDocsContainer, }, + a11y: { test: 'error' }, knobs: { disable: true, }, diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 61f0d9a0d15..553dd3b8fa7 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "12.1.0-pre", + "version": "12.2.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -53,6 +53,7 @@ "clean": "rimraf ./dist ./compiled ./unstable ./package.tgz", "storybook": "storybook dev -p 9001 -c .storybook --no-open", "storybook:build": "storybook build -o ./dist/storybook -c .storybook", + "storybook:test": "test-storybook --url http://localhost:9001", "typecheck": "tsc --emitDeclarationOnly false --noEmit", "prepack": "cp package.json package.json.bak && ALIAS_PACKAGE_NAME=unstable node ../../scripts/prepare-npm-package.js", "postpack": "mv package.json.bak package.json && rimraf ./unstable" @@ -65,12 +66,12 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", - "@floating-ui/react": "0.27.13", - "@grafana/data": "12.1.0-pre", - "@grafana/e2e-selectors": "12.1.0-pre", + "@floating-ui/react": "0.27.14", + "@grafana/data": "12.2.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/i18n": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", + "@grafana/i18n": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", "@hello-pangea/dnd": "18.0.1", "@monaco-editor/react": "4.7.0", "@popperjs/core": "2.11.8", @@ -98,7 +99,7 @@ "micro-memoize": "^4.1.2", "moment": "2.30.1", "monaco-editor": "0.34.1", - "ol": "10.6.0", + "ol": "10.6.1", "prismjs": "1.30.0", "rc-cascader": "3.34.0", "rc-drawer": "7.3.0", @@ -133,7 +134,7 @@ "uwrap": "0.1.1" }, "devDependencies": { - "@babel/core": "7.26.10", + "@babel/core": "7.28.0", "@faker-js/faker": "^9.0.0", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "16.0.1", @@ -152,19 +153,20 @@ "@storybook/preview-api": "^8.6.2", "@storybook/react": "^8.6.2", "@storybook/react-webpack5": "^8.6.2", + "@storybook/test-runner": "^0.23.0", "@storybook/theming": "^8.6.2", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/chance": "^1.1.7", "@types/common-tags": "^1.8.0", "@types/d3": "7.4.3", - "@types/hoist-non-react-statics": "3.3.6", + "@types/hoist-non-react-statics": "3.3.7", "@types/is-hotkey": "0.1.10", "@types/jest": "29.5.14", "@types/mock-raf": "1.0.6", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-color": "3.0.13", @@ -179,10 +181,10 @@ "@types/uuid": "10.0.0", "chance": "^1.1.13", "common-tags": "1.8.2", - "core-js": "3.40.0", + "core-js": "3.44.0", "css-loader": "7.1.2", "csstype": "3.1.3", - "esbuild": "0.25.6", + "esbuild": "0.25.8", "expose-loader": "5.0.1", "fs-extra": "^11.2.0", "mock-raf": "1.0.1", @@ -195,10 +197,10 @@ "rimraf": "6.0.1", "rollup": "^4.22.4", "rollup-plugin-copy": "3.5.0", - "rollup-plugin-esbuild": "6.2.0", + "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", "rollup-plugin-svg-import": "3.0.0", - "sass-loader": "16.0.4", + "sass-loader": "16.0.5", "storybook": "^8.6.2", "style-loader": "4.0.0", "typescript": "5.8.3", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index bf652c8d21f..16c81ca4af8 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -25,6 +25,7 @@ export default [ }), ], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-ui')], + treeshake: false, }, { input: 'src/unstable.ts', @@ -37,5 +38,6 @@ export default [ }), ], output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-ui')], + treeshake: false, }, ]; diff --git a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx index eb6cb427eba..24df1318d55 100644 --- a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx +++ b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.story.tsx @@ -35,6 +35,8 @@ const meta: Meta = { 'validationMessageHorizontalOverflow', ], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { saveErrorMessage: { control: 'text' }, diff --git a/packages/grafana-ui/src/components/Button/Button.story.tsx b/packages/grafana-ui/src/components/Button/Button.story.tsx index 5f26875852e..91746e38dc1 100644 --- a/packages/grafana-ui/src/components/Button/Button.story.tsx +++ b/packages/grafana-ui/src/components/Button/Button.story.tsx @@ -18,6 +18,8 @@ export default { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { size: { diff --git a/packages/grafana-ui/src/components/Carousel/Carousel.story.tsx b/packages/grafana-ui/src/components/Carousel/Carousel.story.tsx index 33a2c762582..68385877df8 100644 --- a/packages/grafana-ui/src/components/Carousel/Carousel.story.tsx +++ b/packages/grafana-ui/src/components/Carousel/Carousel.story.tsx @@ -19,6 +19,8 @@ const meta: Meta = { component: Carousel, parameters: { docs: { page: mdx }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { images: { diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx index 761235d5a22..789b4d04211 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx @@ -49,6 +49,8 @@ const meta: Meta = { 'formatCreateLabel', ], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { onSelect, diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index c3967136503..5b3f6921084 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -18,6 +18,8 @@ const meta: Meta = { controls: { exclude: ['onChange', 'onColorChange'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { enableNamedColors: false, diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx index 0be0684ca33..b0f637b7d7d 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.story.tsx @@ -11,6 +11,8 @@ const meta: Meta = { controls: { exclude: ['onChange', 'onColorChange'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx index 9456242aa8f..fd015178845 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx @@ -17,6 +17,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx index 106dfc19af6..371e431185b 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx @@ -21,6 +21,8 @@ const meta: Meta = { controls: { exclude: ['className', 'onClick', 'onCancel', 'onConfirm'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { buttonText: 'Edit', diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx index ebafcf95e03..78587ebb1dd 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx @@ -19,6 +19,8 @@ const meta: Meta = { controls: { exclude: ['renderMenuItems', 'renderHeader', 'onClose', 'children'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { x: 200, diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index 7e49593d860..f75c4f1f2c8 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -16,7 +16,7 @@ import { getInputStyles, Input } from '../../Input/Input'; import { ScrollContainer } from '../../ScrollContainer/ScrollContainer'; import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle'; import { TimeRangeList } from '../TimeRangePicker/TimeRangeList'; -import { quickOptions } from '../options'; +import { getQuickOptions } from '../options'; import { isRangeValid, @@ -39,8 +39,6 @@ type InputState = { validation: RangeValidation; }; -const validOptions = quickOptions.filter((o) => isRelativeFormat(o.from)); - /** * @internal */ @@ -57,6 +55,7 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) { ref ); const { dialogProps } = useDialog({}, ref); + const validOptions = getQuickOptions().filter((o) => isRelativeFormat(o.from)); // the order of middleware is important! // see https://floating-ui.com/docs/arrow#order diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx index d3678e8dc84..baea8271f34 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx @@ -13,6 +13,8 @@ const meta: Meta = { controls: { exclude: ['onChange'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { value: dateTime(Date.now()), diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx index 7ef005c42c6..67d3af9496a 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx @@ -13,7 +13,7 @@ import { getInputStyles } from '../Input/Input'; import { TimePickerContent } from './TimeRangePicker/TimePickerContent'; import { TimeRangeLabel } from './TimeRangePicker/TimeRangeLabel'; import { WeekStart } from './WeekStartPicker'; -import { quickOptions } from './options'; +import { getQuickOptions } from './options'; import { isValidTimeRange } from './utils'; export interface TimeRangeInputProps { @@ -123,7 +123,7 @@ export const TimeRangeInput = ({ timeZone={timeZone} value={isValidTimeRange(value) ? value : getDefaultTimeRange()} onChange={onRangeChange} - quickOptions={quickOptions} + quickOptions={getQuickOptions()} onChangeTimeZone={onChangeTimeZone} className={styles.content} hideTimeZone={hideTimeZone} diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index 3888b76f6c4..926fcce5615 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -28,7 +28,7 @@ import { Tooltip } from '../Tooltip/Tooltip'; import { TimePickerContent } from './TimeRangePicker/TimePickerContent'; import { TimeZoneDescription } from './TimeZonePicker/TimeZoneDescription'; import { WeekStart } from './WeekStartPicker'; -import { quickOptions } from './options'; +import { getQuickOptions } from './options'; import { useTimeSync } from './utils/useTimeSync'; /** @public */ @@ -189,7 +189,7 @@ export function TimeRangePicker(props: TimeRangePickerProps) { fiscalYearStartMonth={fiscalYearStartMonth} value={value} onChange={onChange} - quickOptions={quickRanges || quickOptions} + quickOptions={quickRanges || getQuickOptions()} history={history} showHistory widthOverride={widthOverride} diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx index 69fe98c09d5..c59806cea13 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx @@ -18,7 +18,7 @@ import { TimeZonePicker } from '../TimeZonePicker'; import { TimeZoneDescription } from '../TimeZonePicker/TimeZoneDescription'; import { TimeZoneOffset } from '../TimeZonePicker/TimeZoneOffset'; import { TimeZoneTitle } from '../TimeZonePicker/TimeZoneTitle'; -import { monthOptions } from '../options'; +import { getMonthOptions } from '../options'; interface Props { timeZone?: TimeZone; @@ -146,7 +146,7 @@ export const TimePickerFooter = (props: Props) => { > { if (onChangeFiscalYearStartMonth) { onChangeFiscalYearStartMonth(value?.value ?? 0); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/mapper.ts b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/mapper.ts index cce0bb76e37..160a1bdbc96 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/mapper.ts +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/mapper.ts @@ -7,6 +7,7 @@ export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): T return rangeUtil.convertRawToRange({ from: option.from, to: option.to }, timeZone, undefined, commonFormat); }; +// TODO: Should we keep these format presets somewhere common? const rangeFormatShort: Intl.DateTimeFormatOptions = { dateStyle: 'short', timeStyle: 'short', diff --git a/packages/grafana-ui/src/components/DateTimePickers/options.ts b/packages/grafana-ui/src/components/DateTimePickers/options.ts index 8f64046cf41..5ef8ef5ab5a 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/options.ts +++ b/packages/grafana-ui/src/components/DateTimePickers/options.ts @@ -1,57 +1,134 @@ import { TimeOption } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { ComboboxOption } from '../Combobox/types'; -export const quickOptions: TimeOption[] = [ - { from: 'now-5m', to: 'now', display: 'Last 5 minutes' }, - { from: 'now-15m', to: 'now', display: 'Last 15 minutes' }, - { from: 'now-30m', to: 'now', display: 'Last 30 minutes' }, - { from: 'now-1h', to: 'now', display: 'Last 1 hour' }, - { from: 'now-3h', to: 'now', display: 'Last 3 hours' }, - { from: 'now-6h', to: 'now', display: 'Last 6 hours' }, - { from: 'now-12h', to: 'now', display: 'Last 12 hours' }, - { from: 'now-24h', to: 'now', display: 'Last 24 hours' }, - { from: 'now-2d', to: 'now', display: 'Last 2 days' }, - { from: 'now-7d', to: 'now', display: 'Last 7 days' }, - { from: 'now-30d', to: 'now', display: 'Last 30 days' }, - { from: 'now-90d', to: 'now', display: 'Last 90 days' }, - { from: 'now-6M', to: 'now', display: 'Last 6 months' }, - { from: 'now-1y', to: 'now', display: 'Last 1 year' }, - { from: 'now-2y', to: 'now', display: 'Last 2 years' }, - { from: 'now-5y', to: 'now', display: 'Last 5 years' }, - { from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday' }, - { from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday' }, - { from: 'now-7d/d', to: 'now-7d/d', display: 'This day last week' }, - { from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week' }, - { from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month' }, - { from: 'now-1Q/fQ', to: 'now-1Q/fQ', display: 'Previous fiscal quarter' }, - { from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year' }, - { from: 'now-1y/fy', to: 'now-1y/fy', display: 'Previous fiscal year' }, - { from: 'now/d', to: 'now/d', display: 'Today' }, - { from: 'now/d', to: 'now', display: 'Today so far' }, - { from: 'now/w', to: 'now/w', display: 'This week' }, - { from: 'now/w', to: 'now', display: 'This week so far' }, - { from: 'now/M', to: 'now/M', display: 'This month' }, - { from: 'now/M', to: 'now', display: 'This month so far' }, - { from: 'now/y', to: 'now/y', display: 'This year' }, - { from: 'now/y', to: 'now', display: 'This year so far' }, - { from: 'now/fQ', to: 'now', display: 'This fiscal quarter so far' }, - { from: 'now/fQ', to: 'now/fQ', display: 'This fiscal quarter' }, - { from: 'now/fy', to: 'now', display: 'This fiscal year so far' }, - { from: 'now/fy', to: 'now/fy', display: 'This fiscal year' }, +export const getQuickOptions: () => TimeOption[] = () => [ + { from: 'now-5m', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-5-mins', 'Last 5 minutes') }, + { + from: 'now-15m', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.last-15-mins', 'Last 15 minutes'), + }, + { + from: 'now-30m', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.last-30-mins', 'Last 30 minutes'), + }, + { from: 'now-1h', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-1-hour', 'Last 1 hour') }, + { from: 'now-3h', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-3-hours', 'Last 3 hours') }, + { from: 'now-6h', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-6-hours', 'Last 6 hours') }, + { + from: 'now-12h', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.last-12-hours', 'Last 12 hours'), + }, + { + from: 'now-24h', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.last-24-hours', 'Last 24 hours'), + }, + { from: 'now-2d', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-2-days', 'Last 2 days') }, + { from: 'now-7d', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-7-days', 'Last 7 days') }, + { from: 'now-30d', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-30-days', 'Last 30 days') }, + { from: 'now-90d', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-90-days', 'Last 90 days') }, + { + from: 'now-6M', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.last-6-months', 'Last 6 months'), + }, + { from: 'now-1y', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-1-year', 'Last 1 year') }, + { from: 'now-2y', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-2-years', 'Last 2 years') }, + { from: 'now-5y', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.last-5-years', 'Last 5 years') }, + { from: 'now-1d/d', to: 'now-1d/d', display: t('grafana-ui.date-time-pickers.quick-options.yesterday', 'Yesterday') }, + { + from: 'now-2d/d', + to: 'now-2d/d', + display: t('grafana-ui.date-time-pickers.quick-options.day-before-yesterday', 'Day before yesterday'), + }, + { + from: 'now-7d/d', + to: 'now-7d/d', + display: t('grafana-ui.date-time-pickers.quick-options.this-day-last-week', 'This day last week'), + }, + { + from: 'now-1w/w', + to: 'now-1w/w', + display: t('grafana-ui.date-time-pickers.quick-options.previous-week', 'Previous week'), + }, + { + from: 'now-1M/M', + to: 'now-1M/M', + display: t('grafana-ui.date-time-pickers.quick-options.previous-month', 'Previous month'), + }, + { + from: 'now-1Q/fQ', + to: 'now-1Q/fQ', + display: t('grafana-ui.date-time-pickers.quick-options.previous-fiscal-quarter', 'Previous fiscal quarter'), + }, + { + from: 'now-1y/y', + to: 'now-1y/y', + display: t('grafana-ui.date-time-pickers.quick-options.previous-year', 'Previous year'), + }, + { + from: 'now-1y/fy', + to: 'now-1y/fy', + display: t('grafana-ui.date-time-pickers.quick-options.previous-fiscal-year', 'Previous fiscal year'), + }, + { from: 'now/d', to: 'now/d', display: t('grafana-ui.date-time-pickers.quick-options.today', 'Today') }, + { from: 'now/d', to: 'now', display: t('grafana-ui.date-time-pickers.quick-options.today-so-far', 'Today so far') }, + { from: 'now/w', to: 'now/w', display: t('grafana-ui.date-time-pickers.quick-options.this-week', 'This week') }, + { + from: 'now/w', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.this-week-so-far', 'This week so far'), + }, + { from: 'now/M', to: 'now/M', display: t('grafana-ui.date-time-pickers.quick-options.this-month', 'This month') }, + { + from: 'now/M', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.this-month-so-far', 'This month so far'), + }, + { from: 'now/y', to: 'now/y', display: t('grafana-ui.date-time-pickers.quick-options.this-year', 'This year') }, + { + from: 'now/y', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.this-year-so-far', 'This year so far'), + }, + { + from: 'now/fQ', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.this-fiscal-quarter-so-far', 'This fiscal quarter so far'), + }, + { + from: 'now/fQ', + to: 'now/fQ', + display: t('grafana-ui.date-time-pickers.quick-options.this-fiscal-quarter', 'This fiscal quarter'), + }, + { + from: 'now/fy', + to: 'now', + display: t('grafana-ui.date-time-pickers.quick-options.this-fiscal-year-so-far', 'This fiscal year so far'), + }, + { + from: 'now/fy', + to: 'now/fy', + display: t('grafana-ui.date-time-pickers.quick-options.this-fiscal-year', 'This fiscal year'), + }, ]; -export const monthOptions: Array> = [ - { label: 'January', value: 0 }, - { label: 'February', value: 1 }, - { label: 'March', value: 2 }, - { label: 'April', value: 3 }, - { label: 'May', value: 4 }, - { label: 'June', value: 5 }, - { label: 'July', value: 6 }, - { label: 'August', value: 7 }, - { label: 'September', value: 8 }, - { label: 'October', value: 9 }, - { label: 'November', value: 10 }, - { label: 'December', value: 11 }, +export const getMonthOptions: () => Array> = () => [ + { label: t('grafana-ui.date-time-pickers.month-options.label-january', 'January'), value: 0 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-february', 'February'), value: 1 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-march', 'March'), value: 2 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-april', 'April'), value: 3 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-may', 'May'), value: 4 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-june', 'June'), value: 5 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-july', 'July'), value: 6 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-august', 'August'), value: 7 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-september', 'September'), value: 8 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-october', 'October'), value: 9 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-november', 'November'), value: 10 }, + { label: t('grafana-ui.date-time-pickers.month-options.label-december', 'December'), value: 11 }, ]; diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx index ac9b70eb14c..9ac5e775590 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx @@ -10,6 +10,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/FormField/FormField.story.tsx b/packages/grafana-ui/src/components/FormField/FormField.story.tsx index 845001dcacc..9d39b20536b 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.story.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.story.tsx @@ -9,6 +9,8 @@ const meta: Meta = { controls: { exclude: ['inputEl'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { inputWidth: 20, diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx index fa4d1fb1d8e..f79388911bd 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx @@ -18,6 +18,8 @@ const meta: Meta = { controls: { exclude: ['value', 'htmlValue'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Forms/Field.story.tsx b/packages/grafana-ui/src/components/Forms/Field.story.tsx index b3accd1e492..e4ed14c79d6 100644 --- a/packages/grafana-ui/src/components/Forms/Field.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.story.tsx @@ -26,6 +26,8 @@ const meta: Meta = { controls: { exclude: ['children', 'className'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx b/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx index fa38cf6521e..69179f5c0a2 100644 --- a/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx @@ -21,6 +21,8 @@ const meta: Meta = { controls: { exclude: ['name', 'keyName', 'control', 'shouldUnregister'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { containerWidth: { control: { type: 'range', min: 100, max: 500, step: 10 } }, diff --git a/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx b/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx index 96e66a07290..6d9037aa852 100644 --- a/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx @@ -21,6 +21,8 @@ const meta: Meta = { controls: { exclude: ['children'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { label: { control: 'text' }, diff --git a/packages/grafana-ui/src/components/Forms/Form.story.tsx b/packages/grafana-ui/src/components/Forms/Form.story.tsx index 7c12beb7465..1ef5d4de60f 100644 --- a/packages/grafana-ui/src/components/Forms/Form.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.story.tsx @@ -23,6 +23,8 @@ export default { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Forms/InlineField.story.tsx b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx index da42f012f3a..9ecff0b22e7 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.story.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx @@ -23,6 +23,8 @@ const meta: Meta = { controls: { exclude: ['htmlFor', 'className', 'children'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx index 4fe04474b16..8f8dadc33f0 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx @@ -14,6 +14,8 @@ const meta: Meta = { controls: { exclude: ['className', 'options', 'value', 'onChange', 'onClick', 'id'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { disabledOptions: { diff --git a/packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx b/packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx index 3173eb4751d..a232b737aed 100644 --- a/packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx +++ b/packages/grafana-ui/src/components/InlineToast/InlineToast.story.tsx @@ -14,6 +14,8 @@ const story: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { referenceElement: { diff --git a/packages/grafana-ui/src/components/Input/Input.story.tsx b/packages/grafana-ui/src/components/Input/Input.story.tsx index eab7f1d3ddc..20da7ce5aac 100644 --- a/packages/grafana-ui/src/components/Input/Input.story.tsx +++ b/packages/grafana-ui/src/components/Input/Input.story.tsx @@ -31,6 +31,8 @@ const meta: Meta = { controls: { exclude: ['prefix', 'suffix', 'addonBefore', 'addonAfter'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { type: 'text', diff --git a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx index e0df9d9782f..c8cf16cf2b6 100644 --- a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx +++ b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx @@ -112,6 +112,8 @@ const meta: Meta> = { controls: { exclude: EXCLUDED_PROPS, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { columns: [ diff --git a/packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx b/packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx index eee4984019c..f7ec6fcfbf5 100644 --- a/packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx +++ b/packages/grafana-ui/src/components/Layout/Grid/Grid.story.tsx @@ -17,6 +17,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { gap: 1, diff --git a/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx b/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx index 5acad6ab10f..14e4e3b5124 100644 --- a/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx +++ b/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx @@ -16,6 +16,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Link/TextLink.story.tsx b/packages/grafana-ui/src/components/Link/TextLink.story.tsx index 6c1b9eb5fff..b353b15674f 100644 --- a/packages/grafana-ui/src/components/Link/TextLink.story.tsx +++ b/packages/grafana-ui/src/components/Link/TextLink.story.tsx @@ -15,6 +15,8 @@ const meta: Meta = { page: mdx, }, controls: { exclude: ['href', 'external'] }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { variant: { diff --git a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx index 3b6e412cb59..1d17511c54d 100644 --- a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx +++ b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.story.tsx @@ -17,6 +17,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx index 129765470af..69736d2368d 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx @@ -27,20 +27,48 @@ FieldTypeMatcherEditor.displayName = 'FieldTypeMatcherEditor'; // Select options for all field types. // This is not eported to the published package, but used internally -export const allFieldTypeIconOptions: Array> = [ - { value: FieldType.number, label: 'Number', icon: getFieldTypeIconName(FieldType.number) }, - { value: FieldType.string, label: 'String', icon: getFieldTypeIconName(FieldType.string) }, - { value: FieldType.time, label: 'Time', icon: getFieldTypeIconName(FieldType.time) }, - { value: FieldType.boolean, label: 'Boolean', icon: getFieldTypeIconName(FieldType.boolean) }, - { value: FieldType.trace, label: 'Traces', icon: getFieldTypeIconName(FieldType.trace) }, - { value: FieldType.enum, label: 'Enum', icon: getFieldTypeIconName(FieldType.enum) }, - { value: FieldType.other, label: 'Other', icon: getFieldTypeIconName(FieldType.other) }, +export const getAllFieldTypeIconOptions: () => Array> = () => [ + { + value: FieldType.number, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-number', 'Number'), + icon: getFieldTypeIconName(FieldType.number), + }, + { + value: FieldType.string, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-string', 'String'), + icon: getFieldTypeIconName(FieldType.string), + }, + { + value: FieldType.time, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-time', 'Time'), + icon: getFieldTypeIconName(FieldType.time), + }, + { + value: FieldType.boolean, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-boolean', 'Boolean'), + icon: getFieldTypeIconName(FieldType.boolean), + }, + { + value: FieldType.trace, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-traces', 'Traces'), + icon: getFieldTypeIconName(FieldType.trace), + }, + { + value: FieldType.enum, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-enum', 'Enum'), + icon: getFieldTypeIconName(FieldType.enum), + }, + { + value: FieldType.other, + label: t('grafana-ui.matchers-ui.get-all-field-type-icon-options.label-other', 'Other'), + icon: getFieldTypeIconName(FieldType.other), + }, ]; const useFieldCounts = (data: DataFrame[]): Map => { return useMemo(() => { const counts: Map = new Map(); - for (const t of allFieldTypeIconOptions) { + for (const t of getAllFieldTypeIconOptions()) { counts.set(t.value!, 0); } for (const frame of data) { @@ -61,7 +89,7 @@ const useSelectOptions = (counts: Map, opt?: string): Array { let found = false; const options: Array> = []; - for (const t of allFieldTypeIconOptions) { + for (const t of getAllFieldTypeIconOptions()) { const count = counts.get(t.value!); const match = opt === t.value; if (count || match) { diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx index 3a7b7146c43..87175cbb057 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx @@ -199,7 +199,9 @@ function getListOfQueryRefIds(data: DataFrame[]): Array> for (const [refId, frames] of queries.entries()) { values.push({ value: refId, - label: `Query: ${refId ?? '(missing refId)'}`, + label: refId + ? t('grafana-ui.matchers-ui.get-list-of-query-ref-ids.label', 'Query: {{refId}}', { refId }) + : t('grafana-ui.matchers-ui.get-list-of-query-ref-ids.label-missing-ref-id', 'Query: (missing refId)'), description: getFramesDescription(frames), }); } @@ -208,11 +210,17 @@ function getListOfQueryRefIds(data: DataFrame[]): Array> } function getFramesDescription(frames: DataFrame[]): string { - return `Frames (${frames.length}): - ${frames - .slice(0, Math.min(3, frames.length)) - .map((x) => getFrameDisplayName(x)) - .join(', ')} ${frames.length > 3 ? '...' : ''}`; + return t( + 'grafana-ui.matchers-ui.get-list-of-query-ref-ids.description', + 'Frames ({{framesCount}}): {{framesNames}}', + { + framesCount: frames.length, + framesNames: `${frames + .slice(0, Math.min(3, frames.length)) + .map((x) => getFrameDisplayName(x)) + .join(', ')} ${frames.length > 3 ? '...' : ''}`, + } + ); } /** diff --git a/packages/grafana-ui/src/components/Menu/Menu.story.tsx b/packages/grafana-ui/src/components/Menu/Menu.story.tsx index d0ea0726b3a..131486ce175 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.story.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.story.tsx @@ -24,6 +24,8 @@ const meta: Meta = { actions: { disabled: true, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Modal/Modal.story.tsx b/packages/grafana-ui/src/components/Modal/Modal.story.tsx index 92e8e13348c..df05238cb3a 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.story.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.story.tsx @@ -21,6 +21,8 @@ const meta: Meta = { controls: { exclude: ['className', 'contentClassName', 'onDismiss', 'onClickBackdrop'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { body: oneLineTrim(`Id incididunt do pariatur qui labore. Sint culpa irure cillum et ullamco proident. Deserunt ipsum velit dolore est enim proident dolore consectetur. Et cillum tempor pariatur et. Est tempor cillum ad id nulla. Cillum ut proident diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx index 4a308a6d0be..d7a85381e9e 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx @@ -11,7 +11,10 @@ import { PageToolbar } from './PageToolbar'; const meta: Meta = { title: 'Navigation/Deprecated/PageToolbar', component: PageToolbar, - parameters: {}, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export const Examples = () => { diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx index e401f94a162..afefc6b7968 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx @@ -31,6 +31,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx index 4b11c87e4ac..f0f672fa120 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx @@ -24,6 +24,8 @@ const meta: Meta = { 'syntaxLoaded', ], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { query: { diff --git a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.mdx b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.mdx index a9b0213ea5e..67ced9d65fe 100644 --- a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.mdx +++ b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.mdx @@ -30,7 +30,6 @@ If you were using additional props, there are roughly 3 categories of changes: - `hideHorizontalTrack` and `hideVerticalTrack` have been removed. - You can achieve the same behavior by setting either `overflowX` or `overflowY` to `hidden`. These names more closely match the CSS properties they represent. - `scrollTop` and `setScrollTop`. - - You can achieve the same behavior by using the `ref` prop to get a reference to the `ScrollContainer` component, and then calling `scrollTo` on that reference. - Before: diff --git a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx index 2d3cf51788a..a6dfbf973eb 100644 --- a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx +++ b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx @@ -14,6 +14,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: {}, args: { diff --git a/packages/grafana-ui/src/components/Segment/Segment.story.tsx b/packages/grafana-ui/src/components/Segment/Segment.story.tsx index 91ae6c47bfb..982776c5415 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.story.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.story.tsx @@ -62,6 +62,10 @@ export const ArrayOptions = () => { const meta: Meta = { title: 'Inputs/Segment', component: Segment, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export const ArrayOptionsWithPrimitiveValue = () => { diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx index 9b64c2fca74..fa4b97b477b 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx @@ -63,6 +63,10 @@ export const ArrayOptions = () => { const meta: Meta = { title: 'Inputs/SegmentAsync', component: SegmentAsync, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export const ArrayOptionsWithPrimitiveValue = () => { diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx index 780f6b4aba2..74af7d58fda 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx @@ -32,6 +32,10 @@ export const BasicInput = () => { const meta: Meta = { title: 'Inputs/SegmentInput', component: SegmentInput, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export const BasicInputWithPlaceholder = () => { diff --git a/packages/grafana-ui/src/components/Select/Select.story.tsx b/packages/grafana-ui/src/components/Select/Select.story.tsx index 7e8388442a1..bf3a92de89a 100644 --- a/packages/grafana-ui/src/components/Select/Select.story.tsx +++ b/packages/grafana-ui/src/components/Select/Select.story.tsx @@ -72,6 +72,8 @@ const meta: Meta = { 'value', ], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { width: 0, diff --git a/packages/grafana-ui/src/components/Select/SelectPerf.story.tsx b/packages/grafana-ui/src/components/Select/SelectPerf.story.tsx index 357eee70561..8a5ff8f9433 100644 --- a/packages/grafana-ui/src/components/Select/SelectPerf.story.tsx +++ b/packages/grafana-ui/src/components/Select/SelectPerf.story.tsx @@ -16,6 +16,10 @@ const meta: Meta = { control: { type: 'number' }, }, }, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export default meta; diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx index eccb916483a..d00648bdfbe 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx @@ -9,6 +9,8 @@ const meta: Meta = { controls: { exclude: ['tooltipAlwaysVisible'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { orientation: { control: { type: 'select', options: ['horizontal', 'vertical'] } }, diff --git a/packages/grafana-ui/src/components/Slider/Slider.story.tsx b/packages/grafana-ui/src/components/Slider/Slider.story.tsx index a4180712444..770038dbc64 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.story.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.story.tsx @@ -12,6 +12,8 @@ const meta: Meta = { knobs: { disabled: true, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { orientation: { control: { type: 'select', options: ['horizontal', 'vertical'] } }, diff --git a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx index 7e20cb9288f..3487a13693f 100644 --- a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx +++ b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx @@ -43,6 +43,8 @@ const meta: Meta = { controls: { exclude: ['onChange', 'stats', 'defaultStat', 'className'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/Switch/Switch.story.tsx b/packages/grafana-ui/src/components/Switch/Switch.story.tsx index ae4786d0fe5..67a9567a2d5 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.story.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.story.tsx @@ -16,6 +16,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { disabled: false, diff --git a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx index 5b2e28c646f..6686f76d49b 100644 --- a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx @@ -1,7 +1,6 @@ import { css } from '@emotion/css'; import { flip, shift, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; -import { ReactElement, useMemo } from 'react'; -import * as React from 'react'; +import { useMemo, ReactNode } from 'react'; import { ActionModel, GrafanaTheme2, LinkModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -16,7 +15,7 @@ import { DataLinksActionsTooltipCoords } from './utils'; interface Props { links: LinkModel[]; actions?: ActionModel[]; - value?: string | ReactElement; + value?: ReactNode; coords: DataLinksActionsTooltipCoords; onTooltipClose?: () => void; } @@ -102,11 +101,7 @@ export const DataLinksActionsTooltip = ({ links, actions, value, coords, onToolt ); }; -export const renderSingleLink = ( - link: LinkModel, - children: string | React.JSX.Element, - className?: string -): React.JSX.Element => { +export const renderSingleLink = (link: LinkModel, children: ReactNode, className?: string): ReactNode => { return ( = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { width: 700, diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/AutoCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/AutoCell.tsx index 45427f703e8..43de0b5bb1e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/AutoCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/AutoCell.tsx @@ -1,13 +1,14 @@ import { formattedValueToString } from '@grafana/data'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { AutoCellProps } from '../types'; export default function AutoCell({ value, field, rowIdx }: AutoCellProps) { const displayValue = field.display!(value); const formattedValue = formattedValueToString(displayValue); - const link = useSingleLink(field, rowIdx); - - return link != null ? renderSingleLink(link, formattedValue) : formattedValue; + return ( + + {formattedValue} + + ); } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx index 69ebfac671c..3f762218553 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx @@ -2,8 +2,7 @@ import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMin import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@grafana/schema'; import { BarGauge } from '../../../BarGauge/BarGauge'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { BarGaugeCellProps } from '../types'; import { extractPixelValue, getCellOptions, getAlignmentFactor } from '../utils'; @@ -47,25 +46,23 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar const alignmentFactors = getAlignmentFactor(field, displayValue, rowIdx!); - const barGaugeComponent = ( - + return ( + + + ); - - const link = useSingleLink(field, rowIdx); - - return link == null ? barGaugeComponent : renderSingleLink(link, barGaugeComponent); }; diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx index a83a623ee88..5fafb04765a 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/DataLinksCell.tsx @@ -1,42 +1,16 @@ -import { css } from '@emotion/css'; - -import { GrafanaTheme2 } from '@grafana/data'; - -import { useStyles2 } from '../../../../themes/ThemeContext'; import { DataLinksCellProps } from '../types'; import { getCellLinks } from '../utils'; export const DataLinksCell = ({ field, rowIdx }: DataLinksCellProps) => { - const styles = useStyles2(getStyles); + const links = getCellLinks(field, rowIdx); - const links = getCellLinks(field, rowIdx!); + if (!links?.length) { + return null; + } - return ( - - ); + return links.map((link, idx) => ( + + {link.title} + + )); }; - -const getStyles = (theme: GrafanaTheme2) => ({ - linkCell: css({ - userSelect: 'text', - whiteSpace: 'nowrap', - fontWeight: theme.typography.fontWeightMedium, - paddingRight: theme.spacing(1.5), - }), -}); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx index e0c1050396a..9953ef0948e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx @@ -4,9 +4,8 @@ import { Property } from 'csstype'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../../../themes/ThemeContext'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; import { TableCellDisplayMode } from '../../types'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { ImageCellProps } from '../types'; const DATALINKS_HEIGHT_OFFSET = 10; @@ -19,10 +18,13 @@ export const ImageCell = ({ cellOptions, field, height, justifyContent, value, r const { alt, title } = cellOptions.type === TableCellDisplayMode.Image ? cellOptions : { alt: undefined, title: undefined }; - const img = {alt}; - const link = useSingleLink(field, rowIdx); - - return
{link == null ? img : renderSingleLink(link, img)}
; + return ( +
+ + {alt} + +
+ ); }; const getStyles = (theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({ diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx deleted file mode 100644 index dc195cbcd67..00000000000 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { css } from '@emotion/css'; -import { Property } from 'csstype'; - -import { GrafanaTheme2 } from '@grafana/data'; - -import { useStyles2 } from '../../../../themes/ThemeContext'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; -import { useSingleLink } from '../hooks'; -import { JSONCellProps } from '../types'; - -export const JSONCell = ({ value, justifyContent, field, rowIdx }: JSONCellProps) => { - const styles = useStyles2(getStyles, justifyContent); - - let displayValue = value; - - // Handle string values that might be JSON - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value); - displayValue = JSON.stringify(parsed, null, ' '); - } catch { - displayValue = value; // Keep original if not valid JSON - } - } else { - // For non-string values, stringify them - try { - displayValue = JSON.stringify(value, null, ' '); - } catch (error) { - // Handle circular references or other stringify errors - displayValue = String(value); - } - } - - const link = useSingleLink(field, rowIdx); - - return
{link == null ? displayValue : renderSingleLink(link, displayValue)}
; -}; - -const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) => ({ - jsonText: css({ - display: 'flex', - cursor: 'pointer', - fontFamily: 'monospace', - justifyContent: justifyContent, - }), -}); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx index 225790cb0bb..4979981995b 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx @@ -12,6 +12,7 @@ import { isDataFrameWithValue, GrafanaTheme2, } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { BarAlignment, GraphDrawStyle, @@ -46,10 +47,10 @@ export const defaultSparklineCellConfig: TableSparklineCellOptions = { export const SparklineCell = (props: SparklineCellProps) => { const { field, value, theme, timeRange, rowIdx, justifyContent, width } = props; const styles = useStyles2(getStyles, justifyContent); - const sparkline = getSparkline(value); + const sparkline = getSparkline(value, field); if (!sparkline) { - return <>{field.config.noValue || 'no data'}; + return <>{field.config.noValue || t('grafana-ui.table.sparkline.no-data', 'no data')}; } // Get the step from the first two values to null-fill the x-axis based on timerange @@ -117,11 +118,11 @@ export const SparklineCell = (props: SparklineCellProps) => { ); }; -function getSparkline(value: unknown): FieldSparkline | undefined { +function getSparkline(value: unknown, field: Field): FieldSparkline | undefined { if (Array.isArray(value)) { return { y: { - name: 'test', + name: `${field.name}-sparkline`, type: FieldType.number, values: value, config: {}, diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx index 2f364bfa732..44221577315 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx @@ -60,6 +60,15 @@ describe('TableNG Cells renderers', () => { config: {}, state: {}, display: jest.fn(() => ({ text: 'black', color: 'white', numeric: 0 })), + // @ts-ignore: this mock works fine for this test. + getLinks: jest.fn(() => [ + { + title: 'example', + href: 'http://example.com', + target: '_blank', + origin: {}, + }, + ]), }; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx index fe4bc7e3f0c..8d444eddf40 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx @@ -11,7 +11,6 @@ import { BarGaugeCell } from './BarGaugeCell'; import { DataLinksCell } from './DataLinksCell'; import { GeoCell } from './GeoCell'; import { ImageCell } from './ImageCell'; -import { JSONCell } from './JSONCell'; import { PillCell } from './PillCell'; import { SparklineCell } from './SparklineCell'; @@ -44,10 +43,6 @@ const SPARKLINE_RENDERER: TableCellRenderer = (props) => ( /> ); -const JSON_RENDERER: TableCellRenderer = (props) => ( - -); - const GEO_RENDERER: TableCellRenderer = (props) => ( ); @@ -86,7 +81,7 @@ const CUSTOM_RENDERER: TableCellRenderer = (props) => { const CELL_RENDERERS: Record = { [TableCellDisplayMode.Sparkline]: SPARKLINE_RENDERER, [TableCellDisplayMode.Gauge]: GAUGE_RENDERER, - [TableCellDisplayMode.JSONView]: JSON_RENDERER, + [TableCellDisplayMode.JSONView]: AUTO_RENDERER, [TableCellDisplayMode.Image]: IMAGE_RENDERER, [TableCellDisplayMode.DataLinks]: DATA_LINKS_RENDERER, [TableCellDisplayMode.Actions]: ACTIONS_RENDERER, @@ -121,12 +116,7 @@ export function getAutoRendererResult(field: Field): TableCellRenderer { const firstValue = field.values[0]; if (isDataFrame(firstValue) && isTimeSeriesFrame(firstValue)) { return SPARKLINE_RENDERER; - } else { - return JSON_RENDERER; } } - if (field.type === FieldType.other) { - return JSON_RENDERER; - } return AUTO_RENDERER; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Filter/Filter.tsx b/packages/grafana-ui/src/components/Table/TableNG/Filter/Filter.tsx index 6f2c55fc3d7..2f219a4dd06 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Filter/Filter.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Filter/Filter.tsx @@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css'; import { useRef, useState } from 'react'; import { Field, GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { useStyles2 } from '../../../../themes/ThemeContext'; import { Icon } from '../../../Icon/Icon'; @@ -61,6 +62,7 @@ export const Filter = ({ className={styles.headerFilter} ref={ref} type="button" + data-testid={selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton} onClick={(ev) => { setPopoverVisible(true); ev.stopPropagation(); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterList.tsx b/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterList.tsx index 783c25f0691..b482f66a60f 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterList.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Filter/FilterList.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; import { GrafanaTheme2, formattedValueToString, getValueFormat, SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; import { useStyles2, useTheme2 } from '../../../../themes/ThemeContext'; @@ -160,7 +161,10 @@ export const FilterList = ({ options, values, caseSensitive, onChange, searchFil > {ItemRenderer} -
+
{/* This is just blocking click events from bubbeling and should not have a keyboard interaction. */} {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */} -
+
{field && } diff --git a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx new file mode 100644 index 00000000000..443053e8134 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx @@ -0,0 +1,32 @@ +import { memo, ReactNode } from 'react'; + +import { Field } from '@grafana/data'; + +import { renderSingleLink } from '../DataLinksActionsTooltip'; + +import { getCellLinks } from './utils'; + +interface MaybeWrapWithLinkProps { + field: Field; + rowIdx: number; + children: ReactNode; +} + +export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWithLinkProps): ReactNode => { + const linksCount = field.config.links?.length ?? 0; + const actionsCount = field.config.actions?.length ?? 0; + + // as real, single link + if (linksCount === 1 && actionsCount === 0) { + let link = (getCellLinks(field, rowIdx) ?? [])[0]; + return renderSingleLink(link, children); + } + // as faux link that acts as hit-area for tooltip activation + else if (linksCount + actionsCount > 0) { + // eslint-disable-next-line jsx-a11y/anchor-is-valid + return {children}; + } + + // raw value + return children; +}); diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 012424e1e28..c84fb2d4194 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -60,7 +60,6 @@ import { getDefaultRowHeight, getDisplayName, getIsNestedTable, - getTextAlign, getVisibleFields, shouldTextOverflow, getApplyToRowBgFn, @@ -72,6 +71,10 @@ import { isCellInspectEnabled, getCellLinks, withDataLinksActionsTooltip, + displayJsonValue, + getAlignment, + getJustifyContent, + TextAlign, } from './utils'; type CellRootRenderer = (key: React.Key, props: CellRendererProps) => React.ReactNode; @@ -185,7 +188,6 @@ export function TableNG(props: TableNGProps) { fields: visibleFields, hasNestedFrames, defaultHeight: defaultRowHeight, - headerHeight, expandedRows, typographyCtx, }); @@ -208,7 +210,11 @@ export function TableNG(props: TableNGProps) { }); // Create a map of column key to text wrap - const footerCalcs = useFooterCalcs(sortedRows, data.fields, { enabled: hasFooter, footerOptions, isCountRowsSet }); + const footerCalcs = useFooterCalcs(sortedRows, visibleFields, { + enabled: hasFooter, + footerOptions, + isCountRowsSet, + }); const applyToRowBgFn = useMemo(() => getApplyToRowBgFn(data.fields, theme) ?? undefined, [data.fields, theme]); const renderRow = useMemo( @@ -307,7 +313,16 @@ export function TableNG(props: TableNGProps) { field.display = getDisplayProcessor({ field, theme }); } - const justifyContent = getTextAlign(field); + // attach JSONCell custom display function to JSONView cell type + if (cellType === TableCellDisplayMode.JSONView || field.type === FieldType.other) { + field.display = displayJsonValue; + } + + // For some cells, "aligning" the cell will mean aligning the inline contents of the cell with + // the text-align css property, and for others, we'll use justify-content to align the cell + // contents with flexbox. We always just get both and provide both when styling the cell. + const textAlign = getAlignment(field); + const justifyContent = getJustifyContent(textAlign); const footerStyles = getFooterStyles(justifyContent); const displayName = getDisplayName(field); const headerCellClass = getHeaderCellStyles(theme, justifyContent); @@ -332,6 +347,7 @@ export function TableNG(props: TableNGProps) { const withTooltip = withDataLinksActionsTooltip(field, cellType); const canBeColorized = cellType === TableCellDisplayMode.ColorBackground || cellType === TableCellDisplayMode.ColorText; + const isMonospace = cellType === TableCellDisplayMode.JSONView; result.colsWithTooltip[displayName] = withTooltip; @@ -344,7 +360,16 @@ export function TableNG(props: TableNGProps) { case TableCellDisplayMode.ColorBackground: case TableCellDisplayMode.ColorText: case TableCellDisplayMode.DataLinks: - cellClass = getCellStyles(theme, justifyContent, shouldWrap, shouldOverflow, withTooltip, canBeColorized); + case TableCellDisplayMode.JSONView: + cellClass = getCellStyles( + theme, + textAlign, + shouldWrap, + shouldOverflow, + canBeColorized, + isMonospace, + cellType === TableCellDisplayMode.DataLinks + ); break; } @@ -621,11 +646,17 @@ export function TableNG(props: TableNGProps) { rows={paginatedRows} headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: noHeader })} headerRowHeight={headerHeight} - onCellClick={({ column, row }, { clientX, clientY, preventGridDefault }) => { + onCellClick={({ column, row }, { clientX, clientY, preventGridDefault, target }) => { // Note: could be column.field; JS says yes, but TS says no! const field = columns[column.idx].field; - if (colsWithTooltip[getDisplayName(field)]) { + if ( + colsWithTooltip[getDisplayName(field)] && + target instanceof HTMLElement && + // this walks up the tree to find either a faux link wrapper or the cell root + // it then only proceeds if we matched the faux link wrapper + target.closest('a[aria-haspopup], .rdg-cell')?.matches('a') + ) { const rowIdx = row.__index; setTooltipState({ coords: { @@ -764,6 +795,8 @@ const getGridStyles = ( '--rdg-header-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary, '--rdg-border-color': theme.colors.border.weak, '--rdg-color': theme.colors.text.primary, + '--rdg-summary-border-color': theme.colors.border.weak, + '--rdg-summary-border-width': '1px', // note: this cannot have any transparency since default cells that // overlay/overflow on hover inherit this background and need to occlude cells below @@ -780,7 +813,27 @@ const getGridStyles = ( border: 'none', - '.rdg-summary-row': { + '.rdg-cell': { + '&:last-child': { + borderInlineEnd: 'none', + }, + }, + + // add a box shadow on hover and selection for all body cells + '& > :not(.rdg-summary-row, .rdg-header-row) > .rdg-cell': { + '&:hover, &[aria-selected=true]': { + boxShadow: theme.shadows.z2, + }, + // selected cells should appear below hovered cells. + '&:hover': { + zIndex: theme.zIndex.tooltip - 2, + }, + '&[aria-selected=true]': { + zIndex: theme.zIndex.tooltip - 3, + }, + }, + + '.rdg-header-row, .rdg-summary-row': { '.rdg-cell': { zIndex: theme.zIndex.tooltip - 1, paddingInline: TABLE.CELL_PADDING, @@ -881,40 +934,39 @@ const getHeaderCellStyles = (theme: GrafanaTheme2, justifyContent: Property.Just const getCellStyles = ( theme: GrafanaTheme2, - justifyContent: Property.JustifyContent, + textAlign: TextAlign, shouldWrap: boolean, shouldOverflow: boolean, - hasTooltip: boolean, - isColorized: boolean + isColorized: boolean, + isMonospace: boolean, + // TODO: replace this with cellTypeStyles: TemplateStringsArray object + isLinkCell: boolean ) => css({ display: 'flex', alignItems: 'center', - justifyContent, + textAlign, + justifyContent: getJustifyContent(textAlign), paddingInline: TABLE.CELL_PADDING, minHeight: '100%', backgroundClip: 'padding-box !important', // helps when cells have a bg color - ...(shouldWrap && { whiteSpace: 'pre-line' }), - ...(hasTooltip && { cursor: 'pointer' }), - - '&:last-child': { - borderInlineEnd: 'none', - }, + ...(shouldWrap && { whiteSpace: isMonospace ? 'pre' : 'pre-line' }), + ...(isMonospace && { fontFamily: 'monospace' }), // should omit if no cell actions, and no shouldOverflow - '&:hover': { + '&:hover, &[aria-selected=true]': { '.table-cell-actions': { display: 'flex', }, ...(shouldOverflow && { - zIndex: theme.zIndex.tooltip - 2, whiteSpace: 'pre-line', height: 'fit-content', minWidth: 'fit-content', + ...(isMonospace && { whiteSpace: 'pre' }), }), }, - [hasTooltip ? '&' : 'a']: { + a: { cursor: 'pointer', ...(isColorized ? { @@ -929,4 +981,22 @@ const getCellStyles = ( }, }), }, + + ...(isLinkCell && { + '> a': { + // display: 'inline', // textWrap ? 'block' : 'inline', + whiteSpace: 'nowrap', + paddingInline: theme.spacing(1), + borderRight: `2px solid ${theme.colors.border.medium}`, + + '&:first-of-type': { + paddingInlineStart: 0, + }, + + '&:last-of-type': { + borderRight: 'none', + paddingInlineEnd: 0, + }, + }, + }), }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts index 094804a3a28..4df9128205a 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts @@ -1,7 +1,7 @@ import { act, renderHook } from '@testing-library/react'; import { varPreLine } from 'uwrap'; -import { Field, FieldType } from '@grafana/data'; +import { cacheFieldDisplayNames, createDataFrame, Field, FieldType } from '@grafana/data'; import { useFilteredRows, @@ -226,11 +226,11 @@ describe('TableNG hooks', () => { describe('useFooterCalcs', () => { const rows = [ - { Field1: 1, Text: 'a', __depth: 0, __index: 0 }, - { Field1: 2, Text: 'b', __depth: 0, __index: 1 }, - { Field1: 3, Text: 'c', __depth: 0, __index: 2 }, - { Field2: 3, Text: 'd', __depth: 0, __index: 3 }, - { Field2: 10, Text: 'e', __depth: 0, __index: 4 }, + { 'Field 1': 1, Text: 'a', __depth: 0, __index: 0 }, + { 'Field 1': 2, Text: 'b', __depth: 0, __index: 1 }, + { 'Field 1': 3, Text: 'c', __depth: 0, __index: 2 }, + { 'Field 2': 3, Text: 'd', __depth: 0, __index: 3 }, + { 'Field 2': 10, Text: 'e', __depth: 0, __index: 4 }, ]; const numericField: Field = { @@ -239,6 +239,7 @@ describe('TableNG hooks', () => { values: [1, 2, 3], config: { custom: {}, + displayName: 'Field 1', }, display: (value: unknown) => ({ text: String(value), @@ -247,7 +248,6 @@ describe('TableNG hooks', () => { prefix: undefined, suffix: undefined, }), - state: {}, getLinks: undefined, }; @@ -255,7 +255,10 @@ describe('TableNG hooks', () => { name: 'Field2', type: FieldType.number, values: [3, 10], - config: { custom: {} }, + config: { + custom: {}, + displayName: 'Field 2', + }, display: (value: unknown) => ({ text: String(value), numeric: Number(value), @@ -263,7 +266,6 @@ describe('TableNG hooks', () => { prefix: undefined, suffix: undefined, }), - state: {}, getLinks: undefined, }; @@ -279,108 +281,151 @@ describe('TableNG hooks', () => { prefix: undefined, suffix: undefined, }), - state: {}, getLinks: undefined, }; it('should calculate sum for numeric fields', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, numericField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: ['sum'] }, - }) - ); + }); + }); expect(result.current).toEqual(['Total', '6']); // 1 + 2 + 3 }); it('should calculate mean for numeric fields', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, numericField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: ['mean'] }, - }) - ); + }); + }); expect(result.current).toEqual(['Mean', '2']); // (1 + 2 + 3) / 3 }); it('should return an empty string for non-numeric fields', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, textField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, textField] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: ['sum'] }, - }) - ); + }); + }); expect(result.current).toEqual(['Total', '']); }); it('should return empty array if no footerOptions are provided', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, textField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: undefined, - }) - ); + }); + }); expect(result.current).toEqual([]); }); it('should return empty array when footer is disabled', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, textField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: false, footerOptions: { show: true, reducer: ['sum'] }, - }) - ); + }); + }); expect(result.current).toEqual([]); }); it('should return empty array when reducer is undefined', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, textField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, textField] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: undefined }, - }) - ); + }); + }); expect(result.current).toEqual([]); }); it('should return empty array when reducer is empty', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, textField], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: [] }, - }) - ); + }); + }); expect(result.current).toEqual([]); }); it('should return empty string if fields array doesnt include this field', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, numericField, numericField2], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: ['sum'], fields: ['Field2', 'Field3'] }, - }) - ); + }); + }); expect(result.current).toEqual(['Total', '', '13']); }); it('should return the calculation if fields array includes this field', () => { - const { result } = renderHook(() => - useFooterCalcs(rows, [textField, numericField, numericField2], { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { enabled: true, footerOptions: { show: true, reducer: ['sum'], fields: ['Field1', 'Field2', 'Field3'] }, - }) - ); + }); + }); expect(result.current).toEqual(['Total', '6', '13']); }); + + it('should return the calculation if fields array includes this field by either name or display name', () => { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [textField, numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { + enabled: true, + footerOptions: { show: true, reducer: ['sum'], fields: ['Field1', 'Field 2'] }, + }); + }); + + expect(result.current).toEqual(['Total', '6', '13']); + }); + + it('should not return the reducer label in the first column if there is a calc to render', () => { + const { result } = renderHook(() => { + const data = createDataFrame({ fields: [numericField, numericField2] }); + cacheFieldDisplayNames([data]); + return useFooterCalcs(rows, data.fields, { + enabled: true, + footerOptions: { show: true, reducer: ['sum'], fields: [] }, + }); + }); + + expect(result.current).toEqual(['6', '13']); + }); }); describe('useHeaderHeight', () => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts index 252eb362e14..e5f30a68d27 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts @@ -2,7 +2,7 @@ import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect, Ref import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid'; import { varPreLine } from 'uwrap'; -import { Field, fieldReducers, FieldType, formattedValueToString, LinkModel, reduceField } from '@grafana/data'; +import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data'; import { useTheme2 } from '../../../themes/ThemeContext'; import { TableCellDisplayMode, TableColumnResizeActionCallback } from '../types'; @@ -17,7 +17,6 @@ import { getColumnTypes, GetMaxWrapCellOptions, getMaxWrapCell, - getCellLinks, } from './utils'; // Helper function to get displayed value @@ -256,6 +255,7 @@ export interface FooterCalcsOptions { export function useFooterCalcs( rows: TableRow[], + // it's very important that this is the _visible_ fields. fields: Field[], { enabled, footerOptions, isCountRowsSet }: FooterCalcsOptions ): string[] { @@ -266,6 +266,8 @@ export function useFooterCalcs( return []; } + const fieldNameSet = footerOptions.fields?.length ? new Set(footerOptions.fields) : null; + return fields.map((field, index) => { if (field.state?.calcs) { delete field.state?.calcs; @@ -275,24 +277,27 @@ export function useFooterCalcs( return index === 0 ? `${rows.length}` : ''; } + let emptyValue = ''; if (index === 0) { const footerCalcReducer = footerReducers[0]; - return footerCalcReducer ? fieldReducers.get(footerCalcReducer).name : ''; + emptyValue = footerCalcReducer ? fieldReducers.get(footerCalcReducer).name : ''; } if (field.type !== FieldType.number) { - return ''; + return emptyValue; } // if field.display is undefined, don't throw const displayFn = field.display; if (!displayFn) { - return ''; + return emptyValue; } - // If fields array is specified, only show footer for fields included in that array - if (footerOptions.fields?.length && !footerOptions.fields?.includes(getDisplayName(field))) { - return ''; + // If fields array is specified, only show footer for fields included in that array. + // the array can include either the display name or the field name. we don't use a field matcher + // because that requires us to drill the data frame down here. + if (fieldNameSet && !fieldNameSet.has(getDisplayName(field)) && !fieldNameSet.has(field.name)) { + return emptyValue; } const calc = footerReducers[0]; @@ -440,7 +445,6 @@ interface UseRowHeightOptions { fields: Field[]; hasNestedFrames: boolean; defaultHeight: number; - headerHeight: number; expandedRows: Set; typographyCtx: TypographyCtx; } @@ -450,7 +454,6 @@ export function useRowHeight({ fields, hasNestedFrames, defaultHeight, - headerHeight, expandedRows, typographyCtx: { calcRowHeight, avgCharWidth }, }: UseRowHeightOptions): number | ((row: TableRow) => number) { @@ -602,13 +605,6 @@ export function useColumnResize( return dataGridResizeHandler; } -export function useSingleLink(field: Field, rowIdx: number): LinkModel | undefined { - const linksCount = field.config.links?.length ?? 0; - const actionsCount = field.config.actions?.length ?? 0; - const shouldShowLink = linksCount === 1 && actionsCount === 0; - return useMemo(() => (shouldShowLink ? (getCellLinks(field, rowIdx) ?? []) : [])[0], [field, shouldShowLink, rowIdx]); -} - export function useScrollbarWidth(ref: RefObject, height: number, renderedRows: TableRow[]) { const [scrollbarWidth, setScrollbarWidth] = useState(0); diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index 742d495b806..bee921c1c71 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -219,13 +219,6 @@ export interface ImageCellProps { rowIdx: number; } -export interface JSONCellProps { - justifyContent: Property.JustifyContent; - value: TableCellValue; - field: Field; - rowIdx: number; -} - export interface DataLinksCellProps { field: Field; rowIdx: number; diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts index 59e58541744..f7bfcf2cc9d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts @@ -1,3 +1,5 @@ +import { SortColumn } from 'react-data-grid'; + import { createDataFrame, createTheme, @@ -25,129 +27,86 @@ import { getComparator, getDefaultRowHeight, getIsNestedTable, - getTextAlign, + getAlignment, + getJustifyContent, migrateTableDisplayModeToCellOptions, getColumnTypes, getMaxWrapCell, + applySort, } from './utils'; describe('TableNG utils', () => { - describe('text alignment', () => { - it('should map alignment options to flex values', () => { - // Test 'left' alignment - const leftField = { - name: 'Value', - type: FieldType.string, - values: [], - config: { - custom: { - align: 'left', + describe('alignment', () => { + it.each(['left', 'center', 'right'] as const)('should return "%s" when configured', (align) => { + expect( + getAlignment({ + name: 'Value', + type: FieldType.string, + values: [], + config: { + custom: { + align, + }, }, - }, - }; - expect(getTextAlign(leftField)).toBe('flex-start'); + }) + ).toBe(align); + }); - // Test 'center' alignment - const centerField = { - name: 'Value', - type: FieldType.string, - values: [], - config: { - custom: { - align: 'center', + it.each([ + { type: FieldType.string, align: 'left' }, + { type: FieldType.number, align: 'right' }, + { type: FieldType.boolean, align: 'left' }, + { type: FieldType.time, align: 'left' }, + ])('should return "$align" for field type $type by default', ({ type, align }) => { + expect( + getAlignment({ + name: 'Test', + type, + values: [], + config: { + custom: {}, }, - }, - }; - expect(getTextAlign(centerField)).toBe('center'); + }) + ).toBe(align); + }); - // Test 'right' alignment - const rightField = { - name: 'Value', - type: FieldType.string, - values: [], - config: { - custom: { - align: 'right', + it.each([ + { cellType: undefined, align: 'right' }, + { cellType: TableCellDisplayMode.Auto, align: 'right' }, + { cellType: TableCellDisplayMode.ColorText, align: 'right' }, + { cellType: TableCellDisplayMode.ColorBackground, align: 'right' }, + { cellType: TableCellDisplayMode.Gauge, align: 'left' }, + { cellType: TableCellDisplayMode.JSONView, align: 'left' }, + { cellType: TableCellDisplayMode.DataLinks, align: 'left' }, + ])('numeric field should return "$align" for cell type "$cellType"', ({ align, cellType }) => { + expect( + getAlignment({ + name: 'Test', + type: FieldType.number, + values: [], + config: { + custom: { + ...(cellType !== undefined + ? { + cellOptions: { + type: cellType, + }, + } + : {}), + }, }, - }, - }; - expect(getTextAlign(rightField)).toBe('flex-end'); + }) + ).toBe(align); }); - it('should default to flex-start when no alignment specified', () => { - const field = { - name: 'Value', - type: FieldType.string, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-start'); - }); - - it('should default to flex-start when no field is specified', () => { - expect(getTextAlign(undefined)).toBe('flex-start'); - }); - - it('should default to flex-end for number types', () => { - const field = { - name: 'Value', - type: FieldType.number, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-end'); - }); - - it('should default to flex-start for string types', () => { - const field = { - name: 'String', - type: FieldType.string, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-start'); - }); - - it('should default to flex-start for enum types', () => { - const field = { - name: 'Enum', - type: FieldType.enum, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-start'); - }); - - it('should default to flex-start for time types', () => { - const field = { - name: 'Time', - type: FieldType.time, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-start'); - }); - - it('should default to flex-start for boolean types', () => { - const field = { - name: 'Active', - type: FieldType.boolean, - values: [], - config: { - custom: {}, - }, - }; - expect(getTextAlign(field)).toBe('flex-start'); + describe('mapping to getJustifyContent', () => { + it.each([ + { align: 'left', expected: 'flex-start' }, + { align: 'center', expected: 'center' }, + { align: 'right', expected: 'flex-end' }, + ] as const)(`should map align "$align" to justifyContent "$expected"`, ({ align, expected }) => { + expect(getJustifyContent(align)).toBe(expected); + }); }); }); @@ -919,6 +878,21 @@ describe('TableNG utils', () => { expect(onClickHandler).not.toHaveBeenCalled(); } ); + + it('should filter out links which contain neither href nor onClick', () => { + const field: Field = { + name: 'test', + type: FieldType.string, + config: {}, + values: ['value1'], + getLinks: (): LinkModel[] => [ + { title: 'Invalid Link', target: '_blank', origin: { datasourceUid: 'test' } } as LinkModel, // No href or onClick + ], + }; + + const links = getCellLinks(field, 0); + expect(links).toEqual([]); + }); }); describe('extractPixelValue', () => { @@ -1113,4 +1087,39 @@ describe('TableNG utils', () => { it.todo('should only apply wrapping on idiomatic break characters (space, -, etc)'); }); + + describe('applySort', () => { + it('sorts by nanos', () => { + const frame = createDataFrame({ + fields: [ + { name: 'time', values: [1, 1, 2], nanos: [100, 99, 0] }, + { name: 'value', values: [10, 20, 30] }, + ], + }); + + const sortColumns: SortColumn[] = [ + { + columnKey: 'time', + direction: 'ASC', + }, + ]; + + const records = applySort(frameToRecords(frame), frame.fields, sortColumns); + + expect(records).toMatchObject([ + { + time: 1, + value: 20, + }, + { + time: 1, + value: 10, + }, + { + time: 2, + value: 30, + }, + ]); + }); + }); }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index 191e9fa76e4..67f4bf497ed 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -11,9 +11,11 @@ import { LinkModel, DisplayValueAlignmentFactors, DataFrame, + DisplayProcessor, } from '@grafana/data'; import { BarGaugeDisplayMode, + FieldTextAlignment, TableCellBackgroundDisplayMode, TableCellDisplayMode, TableCellHeight, @@ -23,10 +25,10 @@ import { getTextColorForAlphaBackground } from '../../../utils/colors'; import { TableCellOptions } from '../types'; import { COLUMN, TABLE } from './constants'; -import { CellColors, TableRow, TableFieldOptionsType, ColumnTypes, FrameToRowsConverter, Comparator } from './types'; +import { CellColors, TableRow, ColumnTypes, FrameToRowsConverter, Comparator } from './types'; /* ---------------------------- Cell calculations --------------------------- */ -export type CellHeightCalculator = (text: string, cellWidth: number) => number; +export type CellNumLinesCalculator = (text: string, cellWidth: number) => number; /** * @internal @@ -128,46 +130,51 @@ export function getMaxWrapCell( * Returns true if text overflow handling should be applied to the cell. */ export function shouldTextOverflow(field: Field): boolean { - let type = getCellOptions(field).type; - - return ( - field.type === FieldType.string && + const cellOptions = getCellOptions(field); + const eligibleCellType = // Tech debt: Technically image cells are of type string, which is misleading (kinda?) - // so we need to ensure we don't apply overflow hover states for type image - type !== TableCellDisplayMode.Image && - type !== TableCellDisplayMode.Pill && - !shouldTextWrap(field) && - !isCellInspectEnabled(field) - ); + // so we need to ensurefield.type === FieldType.string we don't apply overflow hover states for type image + (field.type === FieldType.string && + cellOptions.type !== TableCellDisplayMode.Image && + cellOptions.type !== TableCellDisplayMode.Pill) || + // regardless of the underlying cell type, data links cells have text overflow. + cellOptions.type === TableCellDisplayMode.DataLinks; + + return eligibleCellType && !shouldTextWrap(field) && !isCellInspectEnabled(field); +} + +// we only want to infer justifyContent and textAlign for these cellTypes +const TEXT_CELL_TYPES = new Set([ + TableCellDisplayMode.Auto, + TableCellDisplayMode.ColorText, + TableCellDisplayMode.ColorBackground, +]); + +export type TextAlign = 'left' | 'right' | 'center'; + +/** + * @internal + * Returns the text-align value for inline-displayed cells for a field based on its type and configuration. + */ +export function getAlignment(field: Field): TextAlign { + const align: FieldTextAlignment | undefined = field.config.custom?.align; + + if (!align || align === 'auto') { + if (TEXT_CELL_TYPES.has(getCellOptions(field).type) && field.type === FieldType.number) { + return 'right'; + } + return 'left'; + } + + return align; } /** * @internal - * Returns the text alignment for a field based on its type and configuration. + * Returns the justify-content value for flex-displayed cells for a field based on its type and configuration. */ -export function getTextAlign(field?: Field): Property.JustifyContent { - if (!field) { - return 'flex-start'; - } - - if (field.config.custom) { - const custom: TableFieldOptionsType = field.config.custom; - - switch (custom.align) { - case 'right': - return 'flex-end'; - case 'left': - return 'flex-start'; - case 'center': - return 'center'; - } - } - - if (field.type === FieldType.number) { - return 'flex-end'; - } - - return 'flex-start'; +export function getJustifyContent(textAlign: TextAlign): Property.JustifyContent { + return textAlign === 'center' ? 'center' : textAlign === 'right' ? 'flex-end' : 'flex-start'; } const DEFAULT_CELL_OPTIONS = { type: TableCellDisplayMode.Auto } as const; @@ -335,18 +342,33 @@ export function applySort( return rows; } + const sortNanos = sortColumns.map( + (c) => fields.find((f) => f.type === FieldType.time && getDisplayName(f) === c.columnKey)?.nanos + ); + const compareRows = (a: TableRow, b: TableRow): number => { let result = 0; + for (let i = 0; i < sortColumns.length; i++) { const { columnKey, direction } = sortColumns[i]; const compare = getComparator(columnTypes[columnKey]); const sortDir = direction === 'ASC' ? 1 : -1; result = sortDir * compare(a[columnKey], b[columnKey]); + + if (result === 0) { + const nanos = sortNanos[i]; + + if (nanos !== undefined) { + result = sortDir * (nanos[a.__index] - nanos[b.__index]); + } + } + if (result !== 0) { break; } } + return result; }; @@ -537,7 +559,10 @@ export const processNestedTableRows = ( /** * @internal - * returns the display name of a field + * returns the display name of a field. + * We intentionally do not want to use @grafana/data's getFieldDisplayName here, + * instead we have a call to cacheFieldDisplayNames up in TablePanel to handle this + * before we begin. */ export const getDisplayName = (field: Field): string => { return field.state?.displayName ?? field.name; @@ -625,3 +650,27 @@ export function withDataLinksActionsTooltip(field: Field, cellType: TableCellDis (field.config.links?.length ?? 0) + (field.config.actions?.length ?? 0) > 1 ); } + +export const displayJsonValue: DisplayProcessor = (value: unknown): DisplayValue => { + let displayValue: string; + + // Handle string values that might be JSON + if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + displayValue = JSON.stringify(parsed, null, ' '); + } catch { + displayValue = value; // Keep original if not valid JSON + } + } else { + // For non-string values, stringify them + try { + displayValue = JSON.stringify(value, null, ' '); + } catch (error) { + // Handle circular references or other stringify errors + displayValue = String(value); + } + } + + return { text: displayValue, numeric: Number.NaN }; +}; diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.tsx index 124dfc46af9..494d0e20eee 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.tsx @@ -8,6 +8,10 @@ import { TableInputCSV } from './TableInputCSV'; const meta: Meta = { title: 'Inputs/Deprecated/TableInputCSV', component: TableInputCSV, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; export const basic: StoryFn = () => { diff --git a/packages/grafana-ui/src/components/Tags/TagList.story.tsx b/packages/grafana-ui/src/components/Tags/TagList.story.tsx index 7d8c6094de3..e6f16c07028 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.story.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.story.tsx @@ -14,6 +14,8 @@ const meta: Meta = { controls: { exclude: ['className', 'onClick', 'getAriaLabel'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { displayMax: 3, diff --git a/packages/grafana-ui/src/components/Text/Text.story.tsx b/packages/grafana-ui/src/components/Text/Text.story.tsx index f23056df839..359b7f7853b 100644 --- a/packages/grafana-ui/src/components/Text/Text.story.tsx +++ b/packages/grafana-ui/src/components/Text/Text.story.tsx @@ -13,6 +13,8 @@ const meta: Meta = { docs: { page: mdx, }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { variant: { diff --git a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx index 32fa7f40835..682b9f95a88 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx @@ -11,6 +11,8 @@ const meta: Meta = { showPanel: false, }, docs: {}, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, }; diff --git a/packages/grafana-ui/src/components/ThemeDemos/Typography.story.tsx b/packages/grafana-ui/src/components/ThemeDemos/Typography.story.tsx index 0bc75786461..148d2f9002d 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/Typography.story.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/Typography.story.tsx @@ -9,6 +9,10 @@ import { Text } from '../Text/Text'; const meta: Meta = { title: 'Developers/Typography', + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, }; const FONT_WEIGHTS = [/*100, 200, 300, */ 400, 500 /*600, 700, 800, 900*/]; diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx index 04c66996a26..e200c381deb 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx @@ -18,6 +18,8 @@ const meta: Meta = { controls: { exclude: ['imgSrc', 'imgAlt', 'narrow'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, args: { variant: 'default', diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx index 939329c821f..d73c22c82c4 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx @@ -11,6 +11,8 @@ const meta: Meta = { controls: { exclude: ['legend'], }, + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, }, argTypes: { width: { control: { type: 'range', min: 100, max: 1000 } }, diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx index b9af4d23fc4..cba6f984501 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx @@ -16,6 +16,10 @@ const meta: Meta = { containerWidth: '100%', seriesCount: 5, }, + parameters: { + // TODO fix a11y issue in story and remove this + a11y: { test: 'off' }, + }, argTypes: { containerWidth: { control: { diff --git a/packages/grafana-ui/src/internal/index.ts b/packages/grafana-ui/src/internal/index.ts index 3cffdd2cf92..54bf72d16b0 100644 --- a/packages/grafana-ui/src/internal/index.ts +++ b/packages/grafana-ui/src/internal/index.ts @@ -45,7 +45,7 @@ export { } from '../components/MatchersUI/utils'; export type { FieldMatcherUIRegistryItem } from '../components/MatchersUI/types'; export { RefIDMultiPicker, RefIDPicker, stringsToRegexp } from '../components/MatchersUI/FieldsByFrameRefIdMatcher'; -export { allFieldTypeIconOptions } from '../components/MatchersUI/FieldTypeMatcherEditor'; +export { getAllFieldTypeIconOptions } from '../components/MatchersUI/FieldTypeMatcherEditor'; export { getStyles as getSliderStyles } from '../components/Slider/styles'; export { getSelectStyles } from '../components/Select/getSelectStyles'; diff --git a/packages/grafana-ui/src/options/builder/index.ts b/packages/grafana-ui/src/options/builder/index.ts index 42dd18e4850..98b376794ee 100644 --- a/packages/grafana-ui/src/options/builder/index.ts +++ b/packages/grafana-ui/src/options/builder/index.ts @@ -4,3 +4,4 @@ export * from './legend'; export { addTooltipOptions } from './tooltip'; export * from './text'; export * from './stacking'; +export { addTimeCompareOption } from './timeCompare'; diff --git a/packages/grafana-ui/src/options/builder/timeCompare.tsx b/packages/grafana-ui/src/options/builder/timeCompare.tsx new file mode 100644 index 00000000000..de19457d80f --- /dev/null +++ b/packages/grafana-ui/src/options/builder/timeCompare.tsx @@ -0,0 +1,19 @@ +import { PanelOptionsEditorBuilder } from '@grafana/data'; +import { TimeCompareOptions } from '@grafana/schema'; + +/** + * Adds a generic time comparison option to the panel options editor. + * Can be used by any panel that supports time comparison. + */ +export function addTimeCompareOption( + builder: PanelOptionsEditorBuilder, + defaultValue = false +) { + builder.addBooleanSwitch({ + path: 'timeCompare', + name: 'Enable', + category: ['Time Comparison'], + description: '', + defaultValue, + }); +} diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 42ed8747550..1afd2ff6257 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -1,20 +1,20 @@ module github.com/grafana/grafana/pkg/aggregator -go 1.24.4 +go 1.24.5 require ( - github.com/emicklei/go-restful/v3 v3.11.0 + github.com/emicklei/go-restful/v3 v3.12.1 github.com/grafana/grafana-plugin-sdk-go v0.278.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/grafana/grafana/pkg/semconv v0.0.0-20250514132646-acbc7b54ed9e github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/otel v1.36.0 - k8s.io/api v0.33.2 - k8s.io/apimachinery v0.33.2 - k8s.io/apiserver v0.33.2 - k8s.io/client-go v0.33.2 - k8s.io/component-base v0.33.2 + go.opentelemetry.io/otel v1.37.0 + k8s.io/api v0.33.3 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 + k8s.io/client-go v0.33.3 + k8s.io/component-base v0.33.3 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff sigs.k8s.io/structured-merge-diff/v4 v4.6.0 @@ -43,7 +43,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -67,7 +67,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -81,7 +81,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/magefile/mage v1.15.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -102,7 +102,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -125,23 +125,24 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect @@ -157,9 +158,9 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index f3daf37f19e..808b8b897c1 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -50,8 +50,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -74,8 +74,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -154,8 +154,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a534 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= @@ -199,8 +199,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= @@ -264,8 +264,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= @@ -359,32 +359,32 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -395,6 +395,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -430,8 +434,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -520,30 +524,31 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= -k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= -k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index f0f20e7a7ad..3f057367e4f 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/services/datasources" fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" @@ -60,16 +61,27 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { return &backend.QueryDataResponse{Responses: resp}, nil }, }, - plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{ - PluginList: []pluginstore.Plugin{ - { - JSONData: plugins.JSONData{ - ID: "grafana", + plugincontext.ProvideService( + cfg, + localcache.ProvideService(), + &pluginstore.FakePluginStore{ + PluginList: []pluginstore.Plugin{ + { + JSONData: plugins.JSONData{ + ID: "grafana", + }, }, }, }, - }, &fakeDatasources.FakeCacheService{}, &fakeDatasources.FakeDataSourceService{}, - pluginSettings.ProvideService(dbtest.NewFakeDB(), secretstest.NewFakeSecretsService()), pluginconfig.NewFakePluginRequestConfigProvider()), + &fakeDatasources.FakeCacheService{}, + &fakeDatasources.FakeDataSourceService{}, + pluginSettings.ProvideService( + dbtest.NewFakeDB(), + secretstest.NewFakeSecretsService(), + ), + pluginconfig.NewFakePluginRequestConfigProvider(), + ), + mtdsclient.NewNullMTDatasourceClientBuilder(), ) server := SetupAPITestServer(t, func(hs *HTTPServer) { hs.queryDataService = qds @@ -252,6 +264,7 @@ func TestDataSourceQueryError(t *testing.T) { &fakeDatasources.FakeCacheService{}, ds, pluginSettings.ProvideService(dbtest.NewFakeDB(), secretstest.NewFakeSecretsService()), pluginconfig.NewFakePluginRequestConfigProvider()), + mtdsclient.NewNullMTDatasourceClientBuilder(), ) hs.QuotaService = quotatest.New(false, nil) }) diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 2117d939fb9..a4c00ed10e4 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -596,7 +596,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m := hs.web m.Use(requestmeta.SetupRequestMetadata()) - m.Use(middleware.RequestTracing(hs.tracer)) + m.Use(middleware.RequestTracing(hs.tracer, middleware.SkipTracingPaths)) m.Use(middleware.RequestMetrics(hs.Features, hs.Cfg, hs.promRegister)) m.UseMiddleware(hs.LoggerMiddleware.Middleware()) diff --git a/pkg/api/index.go b/pkg/api/index.go index 19db36d3217..cda365a9537 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -69,8 +69,18 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV // Locale is used for some number and date/time formatting, whereas language is used just for // translating words in the interface acceptLangHeader := c.Req.Header.Get("Accept-Language") + acceptLangHeaderFirstValue := "" + + if len(acceptLangHeader) > 0 { + parts := strings.Split(acceptLangHeader, ",") + acceptLangHeaderFirstValue = parts[0] + } + locale := "en-US" // default to en formatting, but use the accept-lang header or user's preference - var regionalFormat string + if acceptLangHeaderFirstValue != "" { + locale = acceptLangHeaderFirstValue + } + language := "" // frontend will set the default language urlPrefs := getURLPrefs(c) @@ -80,17 +90,27 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV language = prefs.JSONData.Language } - if len(acceptLangHeader) > 0 { - parts := strings.Split(acceptLangHeader, ",") - locale = parts[0] - } - + var regionalFormat string if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagLocaleFormatPreference) { - regionalFormat = "en" // default to "en", not "en-US", matching the regionalFormat code + regionalFormat = "en" + + // We default the regional format (locale) to the Accept-Language header rather than the language preference + // mainly because we want to avoid defaulting to en-US for most users who have not set a preference, and we + // don't have more specific English language preferences yet. + + // Regional format preference order (from most-preferred to least): + // 1. URL parameter + // 2. regionalFormat User preference + // 3. Accept-Language header + // 4. Language preference if urlPrefs.RegionalFormat != "" { regionalFormat = urlPrefs.RegionalFormat } else if prefs.JSONData.RegionalFormat != "" { regionalFormat = prefs.JSONData.RegionalFormat + } else if acceptLangHeaderFirstValue != "" { + regionalFormat = acceptLangHeaderFirstValue + } else if language != "" { + regionalFormat = language } } diff --git a/pkg/api/login.go b/pkg/api/login.go index b6d5a950706..8539b3a08bf 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -7,6 +7,8 @@ import ( "fmt" "net/http" "net/url" + "path" + "regexp" "strings" "github.com/grafana/grafana/pkg/api/response" @@ -39,6 +41,9 @@ var getViewIndex = func() string { return viewIndex } +// Only allow redirects that start with an alphanumerical character, a dash or an underscore. +var redirectRe = regexp.MustCompile(`^/[a-zA-Z0-9-_].*`) + var ( errAbsoluteRedirectTo = errors.New("absolute URLs are not allowed for redirect_to cookie value") errInvalidRedirectTo = errors.New("invalid redirect_to cookie value") @@ -68,6 +73,15 @@ func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error { return errForbiddenRedirectTo } + cleanPath := path.Clean(to.Path) + // "." is what path.Clean returns for empty paths + if cleanPath == "." { + return errForbiddenRedirectTo + } + if to.Path != "/" && !redirectRe.MatchString(cleanPath) { + return errForbiddenRedirectTo + } + // when using a subUrl, the redirect_to should start with the subUrl (which contains the leading slash), otherwise the redirect // will send the user to the wrong location if hs.Cfg.AppSubURL != "" && !strings.HasPrefix(to.Path, hs.Cfg.AppSubURL+"/") { diff --git a/pkg/api/user_token_test.go b/pkg/api/user_token_test.go index 62fa0c2b0db..204ab6af6e6 100644 --- a/pkg/api/user_token_test.go +++ b/pkg/api/user_token_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "net/url" "testing" "time" @@ -20,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web/webtest" ) func TestUserTokenAPIEndpoint(t *testing.T) { @@ -150,6 +152,95 @@ func TestUserTokenAPIEndpoint(t *testing.T) { }) } +func TestHTTPServer_RotateUserAuthTokenRedirect(t *testing.T) { + redirectTestCases := []struct { + name string + redirectUrl string + expectedUrl string + }{ + // Valid redirects should be preserved + {"valid root path", "/", "/"}, + {"valid simple path", "/hello", "/hello"}, + {"valid single char path", "/a", "/a"}, + {"valid nested path", "/asd/hello", "/asd/hello"}, + + // Invalid redirects should be converted to root + {"backslash domain", `/\grafana.com`, "/"}, + {"traversal backslash domain", `/a/../\grafana.com`, "/"}, + {"double slash", "//grafana", "/"}, + {"missing initial slash", "missingInitialSlash", "/"}, + {"parent directory", "/../", "/"}, + } + + sessionTestCases := []struct { + name string + useSessionStorageRedirect bool + }{ + {"when useSessionStorageRedirect is enabled", true}, + {"when useSessionStorageRedirect is disabled", false}, + } + + for _, sessionCase := range sessionTestCases { + t.Run(sessionCase.name, func(t *testing.T) { + for _, redirectCase := range redirectTestCases { + t.Run(redirectCase.name, func(t *testing.T) { + server := SetupAPITestServer(t, func(hs *HTTPServer) { + cfg := setting.NewCfg() + cfg.LoginCookieName = "grafana_session" + cfg.LoginMaxLifetime = 10 * time.Hour + hs.Cfg = cfg + hs.log = log.New() + hs.AuthTokenService = &authtest.FakeUserAuthTokenService{ + RotateTokenProvider: func(ctx context.Context, cmd auth.RotateCommand) (*auth.UserToken, error) { + return &auth.UserToken{UnhashedToken: "new"}, nil + }, + } + }) + + redirectToQuery := url.QueryEscape(redirectCase.redirectUrl) + urlString := "/user/auth-tokens/rotate" + + if sessionCase.useSessionStorageRedirect { + urlString = urlString + "?redirectTo=" + redirectToQuery + } + + req := server.NewGetRequest(urlString) + req.AddCookie(&http.Cookie{Name: "grafana_session", Value: "123", Path: "/"}) + + if sessionCase.useSessionStorageRedirect { + req = webtest.RequestWithWebContext(req, &contextmodel.ReqContext{UseSessionStorageRedirect: true}) + } else { + req.AddCookie(&http.Cookie{Name: "redirect_to", Value: redirectToQuery, Path: "/"}) + } + + var redirectStatusCode int + var redirectLocation string + + server.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if len(via) > 1 { + // Stop after first redirect + return http.ErrUseLastResponse + } + + if req.Response == nil { + return nil + } + redirectStatusCode = req.Response.StatusCode + redirectLocation = req.Response.Header.Get("Location") + return nil + } + res, err := server.Send(req) + require.NoError(t, err) + assert.Equal(t, 302, redirectStatusCode) + assert.Equal(t, redirectCase.expectedUrl, redirectLocation) + + require.NoError(t, res.Body.Close()) + }) + } + }) + } +} + func TestHTTPServer_RotateUserAuthToken(t *testing.T) { type testCase struct { desc string diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 8a1c0902f6a..a9b83dc6e00 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apimachinery -go 1.24.4 +go 1.24.5 require ( github.com/go-jose/go-jose/v3 v3.0.4 // @grafana/identity-access-team @@ -8,17 +8,17 @@ require ( github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.33.2 - k8s.io/apiserver v0.33.2 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + k8s.io/utils v0.0.0-20241210054802-24370beab758 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -28,7 +28,7 @@ require ( github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect @@ -38,12 +38,14 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/net v0.41.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect @@ -51,8 +53,8 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 9ea794ad760..b814304bcc7 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -2,15 +2,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -47,8 +47,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -79,16 +79,18 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -115,8 +117,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -166,22 +168,23 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/pkg/apis/provisioning/v0alpha1/jobs.go b/pkg/apis/provisioning/v0alpha1/jobs.go index f9801049cf4..ceea231400b 100644 --- a/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/pkg/apis/provisioning/v0alpha1/jobs.go @@ -37,6 +37,9 @@ const ( // JobActionMigrate acts like JobActionExport, then JobActionPull. It also tries to preserve the history. JobActionMigrate JobAction = "migrate" + + // JobActionDelete deletes files in the remote repository + JobActionDelete JobAction = "delete" ) // +enum @@ -54,10 +57,13 @@ const ( // Finished with errors JobStateError JobState = "error" + + // Finished with some non-critical errors + JobStateWarning JobState = "warning" ) func (j JobState) Finished() bool { - return j == JobStateSuccess || j == JobStateError + return j == JobStateSuccess || j == JobStateError || j == JobStateWarning } type JobSpec struct { @@ -78,6 +84,9 @@ type JobSpec struct { // Required when the action is `migrate` Migrate *MigrateJobOptions `json:"migrate,omitempty"` + + // Delete when the action is `delete` + Delete *DeleteJobOptions `json:"delete,omitempty"` } type PullRequestJobOptions struct { @@ -100,12 +109,17 @@ type SyncJobOptions struct { } type ExportJobOptions struct { + // Message to use when committing the changes in a single commit + Message string `json:"message,omitempty"` + // The source folder (or empty) to export Folder string `json:"folder,omitempty"` + // FIXME: we should validate this in admission hooks // Target branch for export (only git) Branch string `json:"branch,omitempty"` + // FIXME: we should validate this in admission hooks // Prefix in target file system Path string `json:"path,omitempty"` } @@ -113,6 +127,20 @@ type ExportJobOptions struct { type MigrateJobOptions struct { // Preserve history (if possible) History bool `json:"history,omitempty"` + + // Message to use when committing the changes in a single commit + Message string `json:"message,omitempty"` +} + +type DeleteJobOptions struct { + // Ref to the branch or commit hash to delete from + Ref string `json:"ref,omitempty"` + // Paths to be deleted. Examples: + // - dashboard.json (for a file) + // - a/b/c/other-dashboard.json (for a file) + // - nested/deep/ (for a directory) + // FIXME: we should validate this in admission hooks + Paths []string `json:"paths,omitempty"` } // The job status diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/pkg/apis/provisioning/v0alpha1/types.go index 5ea21495add..edcf2f790fa 100644 --- a/pkg/apis/provisioning/v0alpha1/types.go +++ b/pkg/apis/provisioning/v0alpha1/types.go @@ -295,6 +295,7 @@ const ( ResourceActionCreate ResourceAction = "create" ResourceActionUpdate ResourceAction = "update" ResourceActionDelete ResourceAction = "delete" + ResourceActionMove ResourceAction = "move" ) // This is a container type for any resource type diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 30fd560a597..bed49953b63 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -48,6 +48,27 @@ func (in *BitbucketRepositoryConfig) DeepCopy() *BitbucketRepositoryConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeleteJobOptions) DeepCopyInto(out *DeleteJobOptions) { + *out = *in + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteJobOptions. +func (in *DeleteJobOptions) DeepCopy() *DeleteJobOptions { + if in == nil { + return nil + } + out := new(DeleteJobOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ErrorDetails) DeepCopyInto(out *ErrorDetails) { *out = *in @@ -370,6 +391,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) { *out = new(MigrateJobOptions) **out = **in } + if in.Delete != nil { + in, out := &in.Delete, &out.Delete + *out = new(DeleteJobOptions) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index cdac15c0c6f..b81342c30c3 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -16,6 +16,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA return map[string]common.OpenAPIDefinition{ "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref), + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref), @@ -155,6 +156,40 @@ func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common. } } +func schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ref": { + SchemaProps: spec.SchemaProps{ + Description: "Ref to the branch or commit hash to delete from", + Type: []string{"string"}, + Format: "", + }, + }, + "paths": { + SchemaProps: spec.SchemaProps{ + Description: "Paths to be deleted. Examples: - dashboard.json (for a file) - a/b/c/other-dashboard.json (for a file) - nested/deep/ (for a directory) FIXME: we should validate this in admission hooks", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -193,6 +228,13 @@ func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.Reference SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message to use when committing the changes in a single commit", + Type: []string{"string"}, + Format: "", + }, + }, "folder": { SchemaProps: spec.SchemaProps{ Description: "The source folder (or empty) to export", @@ -202,14 +244,14 @@ func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.Reference }, "branch": { SchemaProps: spec.SchemaProps{ - Description: "Target branch for export (only git)", + Description: "FIXME: we should validate this in admission hooks Target branch for export (only git)", Type: []string{"string"}, Format: "", }, }, "path": { SchemaProps: spec.SchemaProps{ - Description: "Prefix in target file system", + Description: "FIXME: we should validate this in admission hooks Prefix in target file system", Type: []string{"string"}, Format: "", }, @@ -836,10 +878,10 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback) Properties: map[string]spec.Schema{ "action": { SchemaProps: spec.SchemaProps{ - Description: "Possible enum values:\n - `\"migrate\"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history.\n - `\"pr\"` adds additional useful information to a PR, such as comments with preview links and rendered images.\n - `\"pull\"` replicates the remote branch in the local copy of the repository.\n - `\"push\"` replicates the local copy of the repository in the remote branch.", + Description: "Possible enum values:\n - `\"delete\"` deletes files in the remote repository\n - `\"migrate\"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history.\n - `\"pr\"` adds additional useful information to a PR, such as comments with preview links and rendered images.\n - `\"pull\"` replicates the remote branch in the local copy of the repository.\n - `\"push\"` replicates the local copy of the repository in the remote branch.", Type: []string{"string"}, Format: "", - Enum: []interface{}{"migrate", "pr", "pull", "push"}, + Enum: []interface{}{"delete", "migrate", "pr", "pull", "push"}, }, }, "repository": { @@ -873,11 +915,17 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback) Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions"), }, }, + "delete": { + SchemaProps: spec.SchemaProps{ + Description: "Delete when the action is `delete`", + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions"}, + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions"}, } } @@ -890,10 +938,10 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac Properties: map[string]spec.Schema{ "state": { SchemaProps: spec.SchemaProps{ - Description: "Possible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"working\"` The job is running", + Description: "Possible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running", Type: []string{"string"}, Format: "", - Enum: []interface{}{"error", "pending", "success", "working"}, + Enum: []interface{}{"error", "pending", "success", "warning", "working"}, }, }, "started": { @@ -1030,6 +1078,13 @@ func schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref common.Referenc Format: "", }, }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message to use when committing the changes in a single commit", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -1742,10 +1797,10 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref common.ReferenceC }, "action": { SchemaProps: spec.SchemaProps{ - Description: "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"update\"`", + Description: "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"move\"`\n - `\"update\"`", Type: []string{"string"}, Format: "", - Enum: []interface{}{"create", "delete", "update"}, + Enum: []interface{}{"create", "delete", "move", "update"}, }, }, "dryRun": { @@ -2135,11 +2190,11 @@ func schema_pkg_apis_provisioning_v0alpha1_SyncStatus(ref common.ReferenceCallba Properties: map[string]spec.Schema{ "state": { SchemaProps: spec.SchemaProps{ - Description: "pending, running, success, error\n\nPossible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"working\"` The job is running", + Description: "pending, running, success, error\n\nPossible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running", Default: "", Type: []string{"string"}, Format: "", - Enum: []interface{}{"error", "pending", "success", "working"}, + Enum: []interface{}{"error", "pending", "success", "warning", "working"}, }, }, "job": { diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index 6c41753b403..fc982653184 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,4 @@ +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,FileList,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,HistoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index dc92b567d4b..3d1557516a4 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -1,22 +1,22 @@ module github.com/grafana/grafana/pkg/apiserver -go 1.24.4 +go 1.24.5 require ( github.com/google/go-cmp v0.7.0 github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 - github.com/grafana/grafana-app-sdk/logging v0.39.1 + github.com/grafana/grafana-app-sdk/logging v0.39.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 - k8s.io/apimachinery v0.33.2 - k8s.io/apiserver v0.33.2 - k8s.io/component-base v0.33.2 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 + k8s.io/component-base v0.33.3 k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + k8s.io/utils v0.0.0-20241210054802-24370beab758 sigs.k8s.io/structured-merge-diff/v4 v4.6.0 ) @@ -28,11 +28,11 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -47,12 +47,12 @@ require ( github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -62,7 +62,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -73,18 +73,19 @@ require ( go.etcd.io/etcd/client/v3 v3.5.21 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.26.0 // indirect @@ -98,11 +99,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.2 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 8006ceabbca..369b533e3a1 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -23,8 +23,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -38,8 +38,8 @@ github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQr github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -84,8 +84,8 @@ github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/ github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= -github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= -github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk/logging v0.39.3 h1:mMrcYahnoRu7blKyL/ZVcgv7WCiI2CqxODYh8tBFUgY= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -94,8 +94,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a534 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -116,8 +116,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -148,8 +148,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= @@ -201,26 +201,26 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -231,6 +231,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -272,8 +276,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -364,30 +368,31 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= -k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= -k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/pkg/build/e2e-playwright/e2e.go b/pkg/build/e2e-playwright/e2e.go index 35579e8dd2f..81044598d3f 100644 --- a/pkg/build/e2e-playwright/e2e.go +++ b/pkg/build/e2e-playwright/e2e.go @@ -32,12 +32,17 @@ func RunTest( ) (*dagger.Container, error) { playwrightCommand := buildPlaywrightCommand(opts) + grafanaHost, err := opts.GrafanaService.Hostname(ctx) + if err != nil { + return nil, err + } + e2eContainer := opts.FrontendContainer. WithWorkdir("/src"). WithDirectory("/src", opts.HostSrc). WithMountedCache(".nx", d.CacheVolume("nx-cache")). - WithEnvVariable("HOST", grafanaHost). - WithEnvVariable("PORT", fmt.Sprint(grafanaPort)). + WithEnvVariable("CI", "true"). + WithEnvVariable("GRAFANA_URL", fmt.Sprintf("http://%s:%d", grafanaHost, grafanaPort)). WithServiceBinding(grafanaHost, opts.GrafanaService). WithEnvVariable("bustcache", "1"). WithEnvVariable("PLAYWRIGHT_HTML_OPEN", "never"). diff --git a/pkg/build/e2e-playwright/main.go b/pkg/build/e2e-playwright/main.go index 09384c8614b..0a0c2cb62bb 100644 --- a/pkg/build/e2e-playwright/main.go +++ b/pkg/build/e2e-playwright/main.go @@ -12,7 +12,6 @@ import ( ) var ( - grafanaHost = "grafana" grafanaPort = 3001 ) diff --git a/pkg/build/go.mod b/pkg/build/go.mod index b1c6fdea7f2..14dd91a5033 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build -go 1.24.4 +go 1.24.5 // Override docker/docker to avoid: // go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires @@ -9,7 +9,7 @@ replace github.com/docker/docker => github.com/moby/moby v27.5.1+incompatible require ( cloud.google.com/go/storage v1.55.0 // @grafana/grafana-backend-group - github.com/Masterminds/semver/v3 v3.3.1 // @grafana/grafana-developer-enablement-squad + github.com/Masterminds/semver/v3 v3.4.0 // @grafana/grafana-developer-enablement-squad github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources github.com/docker/docker v28.1.1+incompatible // @grafana/grafana-developer-enablement-squad github.com/drone/drone-cli v1.8.0 // @grafana/grafana-developer-enablement-squad @@ -22,13 +22,13 @@ require ( github.com/urfave/cli v1.22.16 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.6 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect; @grafana/plugins-platform-backend - go.opentelemetry.io/otel v1.36.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.36.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.36.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.37.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.37.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel/trace v1.37.0 // @grafana/grafana-backend-group golang.org/x/crypto v0.39.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.41.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.30.0 // @grafana/identity-access-team - golang.org/x/sync v0.15.0 // @grafana/alerting-backend + golang.org/x/sync v0.16.0 // @grafana/alerting-backend golang.org/x/text v0.26.0 // indirect; @grafana/grafana-backend-group golang.org/x/time v0.11.0 // indirect; @grafana/grafana-backend-group google.golang.org/api v0.235.0 // @grafana/grafana-backend-group @@ -56,7 +56,7 @@ require ( github.com/drone/runner-go v1.12.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect - github.com/go-logr/logr v1.4.2 // indirect; @grafana/grafana-app-platform-squad + github.com/go-logr/logr v1.4.3 // indirect; @grafana/grafana-app-platform-squad github.com/go-logr/stdr v1.2.2 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect @@ -67,8 +67,8 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/sys v0.33.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect; @grafana/grafana-backend-group @@ -103,7 +103,7 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/go-jose/go-jose/v4 v4.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect @@ -118,15 +118,15 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/pkg/build/go.sum b/pkg/build/go.sum index fa9044c8415..9392a9616ff 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -44,8 +44,8 @@ github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -127,8 +127,8 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -171,8 +171,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7 github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -267,42 +267,42 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8F go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -337,8 +337,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 1c3a3bf1206..7035d2469ac 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build/wire -go 1.24.4 +go 1.24.5 require ( github.com/google/go-cmp v0.7.0 @@ -11,5 +11,5 @@ require ( require ( golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index 0b2f45ba30a..dd031067566 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -6,7 +6,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go index b2fac806a45..8e46423be87 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go @@ -51,7 +51,6 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err Resources: []schema.GroupResource{ {Group: folders.GROUP, Resource: folders.RESOURCE}, {Group: dashboard.GROUP, Resource: dashboard.DASHBOARD_RESOURCE}, - {Group: dashboard.GROUP, Resource: dashboard.LIBRARY_PANEL_RESOURCE}, }, LargeObjects: nil, // TODO... from config Progress: func(count int, msg string) { diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index 5e0af708098..53ead74b2a5 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/codegen -go 1.24.4 +go 1.24.5 require ( cuelang.org/go v0.11.1 @@ -31,7 +31,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect @@ -48,7 +48,7 @@ require ( github.com/yalue/merged_fs v1.3.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/text v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 8196c0310c9..3eeaf7e9d53 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -57,8 +57,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -102,8 +102,8 @@ golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= diff --git a/pkg/expr/dataplane_test.go b/pkg/expr/dataplane_test.go index 56039aa867c..ac3e7c42f95 100644 --- a/pkg/expr/dataplane_test.go +++ b/pkg/expr/dataplane_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -70,6 +71,7 @@ func framesPassThroughService(t *testing.T, frames data.Frames) (data.Frames, er Features: features, Tracer: tracing.InitializeTracerForTest(), }, + mtDatasourceClientBuilder: mtdsclient.NewNullMTDatasourceClientBuilder(), } queries := []Query{{ RefID: "A", diff --git a/pkg/expr/ml.go b/pkg/expr/ml.go index 5903857ae2b..bee5d18ad22 100644 --- a/pkg/expr/ml.go +++ b/pkg/expr/ml.go @@ -134,7 +134,7 @@ func (m *MLNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s * return result, err } -func (s *Service) buildMLNode(dp *simple.DirectedGraph, rn *rawNode, req *Request) (Node, error) { +func (s *Service) buildMLNode(_ *simple.DirectedGraph, rn *rawNode, req *Request) (Node, error) { if rn.TimeRange == nil { return nil, errors.New("time range must be specified") } diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 265bfa347b5..83212715625 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -213,7 +213,7 @@ func (dn *DSNode) NeedsVars() []string { return []string{} } -func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Request) (*DSNode, error) { +func (s *Service) buildDSNode(_ *simple.DirectedGraph, rn *rawNode, req *Request) (*DSNode, error) { if rn.TimeRange == nil { return nil, fmt.Errorf("time range must be specified for refID %s", rn.RefID) } @@ -361,17 +361,12 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s ctx, span := s.tracer.Start(ctx, "SSE.ExecuteDatasourceQuery") defer span.End() - pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, dn.datasource.Type, dn.request.User, dn.datasource) - if err != nil { - return mathexp.Results{}, err - } span.SetAttributes( attribute.String("datasource.type", dn.datasource.Type), attribute.String("datasource.uid", dn.datasource.UID), ) req := &backend.QueryDataRequest{ - PluginContext: pCtx, Queries: []backend.DataQuery{ { RefID: dn.refID, @@ -399,9 +394,43 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s s.metrics.DSRequests.WithLabelValues(respStatus, fmt.Sprintf("%t", useDataplane), dn.datasource.Type).Inc() }() - resp, err := s.dataService.QueryData(ctx, req) - if err != nil { - return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) + var resp *backend.QueryDataResponse + mtDSClient, ok := s.mtDatasourceClientBuilder.BuildClient(dn.datasource.Type, dn.datasource.UID) + if !ok { // use single tenant client + pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, dn.datasource.Type, dn.request.User, dn.datasource) + if err != nil { + return mathexp.Results{}, err + } + req.PluginContext = pCtx + resp, err = s.dataService.QueryData(ctx, req) + if err != nil { + return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) + } + } else { + // transform request from backend.QueryDataRequest to k8s request + k8sReq := &data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: req.Queries[0].TimeRange.From.Format(time.RFC3339), + To: req.Queries[0].TimeRange.To.Format(time.RFC3339), + }, + } + for _, q := range req.Queries { + var dataQuery data.DataQuery + err := json.Unmarshal(q.JSON, &dataQuery) + if err != nil { + return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) + } + + k8sReq.Queries = append(k8sReq.Queries, dataQuery) + } + var err error + // make the query with a mt client + resp, err = mtDSClient.QueryData(ctx, *k8sReq) + + // handle error + if err != nil { + return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) + } } dataFrames, err := getResponseFrame(logger, resp, dn.refID) diff --git a/pkg/expr/service.go b/pkg/expr/service.go index e3ea54ea056..19b124b70d2 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/setting" ) @@ -65,8 +66,9 @@ type Service struct { pluginsClient backend.CallResourceHandler - tracer tracing.Tracer - metrics *metrics.ExprMetrics + tracer tracing.Tracer + metrics *metrics.ExprMetrics + mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder } type pluginContextProvider interface { @@ -75,7 +77,7 @@ type pluginContextProvider interface { } func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider, - features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer tracing.Tracer) *Service { + features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer tracing.Tracer, builder mtdsclient.MTDatasourceClientBuilder) *Service { return &Service{ cfg: cfg, dataService: pluginClient, @@ -88,6 +90,7 @@ func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider Features: features, Tracer: tracer, }, + mtDatasourceClientBuilder: builder, } } diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 492f1ce4389..779791a7812 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -255,5 +256,6 @@ func newMockQueryService(responses map[string]backend.DataResponse, queries []Qu Features: features, Tracer: tracing.InitializeTracerForTest(), }, + mtDatasourceClientBuilder: mtdsclient.NewNullMTDatasourceClientBuilder(), }, &Request{Queries: queries, User: &user.SignedInUser{}} } diff --git a/pkg/middleware/org_redirect.go b/pkg/middleware/org_redirect.go index b794f9acfdf..8ce90ac643d 100644 --- a/pkg/middleware/org_redirect.go +++ b/pkg/middleware/org_redirect.go @@ -3,6 +3,8 @@ package middleware import ( "fmt" "net/http" + "path" + "regexp" "strconv" "github.com/grafana/grafana/pkg/services/contexthandler" @@ -11,6 +13,9 @@ import ( "github.com/grafana/grafana/pkg/web" ) +// Only allow redirects that start with an alphanumerical character, a dash or an underscore. +var redirectRe = regexp.MustCompile(`^/?[a-zA-Z0-9-_].*`) + // OrgRedirect changes org and redirects users if the // querystring `orgId` doesn't match the active org. func OrgRedirect(cfg *setting.Cfg, userSvc user.Service) web.Handler { @@ -31,6 +36,11 @@ func OrgRedirect(cfg *setting.Cfg, userSvc user.Service) web.Handler { return } + if !validRedirectPath(c.Req.URL.Path) { + // Do not switch orgs or perform the redirect because the new path is not valid + return + } + if err := userSvc.Update(ctx.Req.Context(), &user.UpdateUserCommand{UserID: ctx.UserID, OrgID: &orgId}); err != nil { if ctx.IsApiRequest() { ctx.JsonApiErr(404, "Not found", nil) @@ -54,3 +64,8 @@ func OrgRedirect(cfg *setting.Cfg, userSvc user.Service) web.Handler { c.Redirect(newURL, 302) } } + +func validRedirectPath(p string) bool { + cleanPath := path.Clean(p) + return cleanPath == "." || cleanPath == "/" || redirectRe.MatchString(cleanPath) +} diff --git a/pkg/middleware/org_redirect_test.go b/pkg/middleware/org_redirect_test.go index 4087eeb91c6..06800c5ac69 100644 --- a/pkg/middleware/org_redirect_test.go +++ b/pkg/middleware/org_redirect_test.go @@ -2,6 +2,7 @@ package middleware import ( "fmt" + "net/url" "testing" "github.com/stretchr/testify/require" @@ -23,6 +24,12 @@ func TestOrgRedirectMiddleware(t *testing.T) { expStatus: 302, expLocation: "/?orgId=3", }, + { + desc: "when setting a correct org for the user with an empty path", + input: "?orgId=3", + expStatus: 302, + expLocation: "/?orgId=3", + }, { desc: "when setting a correct org for the user with '&kiosk'", input: "/?orgId=3&kiosk", @@ -64,6 +71,16 @@ func TestOrgRedirectMiddleware(t *testing.T) { require.Equal(t, 404, sc.resp.Code) }) + middlewareScenario(t, "when redirecting to an invalid path", func(t *testing.T, sc *scenarioContext) { + sc.withIdentity(&authn.Identity{}) + + path := url.QueryEscape(`/\example.com`) + sc.m.Get(url.QueryEscape(path), sc.defaultHandler) + sc.fakeReq("GET", fmt.Sprintf("%s?orgId=3", path)).exec() + + require.Equal(t, 404, sc.resp.Code) + }) + middlewareScenario(t, "works correctly when grafana is served under a subpath", func(t *testing.T, sc *scenarioContext) { sc.withIdentity(&authn.Identity{}) diff --git a/pkg/middleware/request_tracing.go b/pkg/middleware/request_tracing.go index 3063dbdd51e..fd862be2b5a 100644 --- a/pkg/middleware/request_tracing.go +++ b/pkg/middleware/request_tracing.go @@ -71,14 +71,23 @@ func RouteOperationName(req *http.Request) (string, bool) { return "", false } -func RequestTracing(tracer tracing.Tracer) web.Middleware { +// Paths that don't need tracing spans applied to them because of the +// little value that would provide us +func SkipTracingPaths(req *http.Request) bool { + return strings.HasPrefix(req.URL.Path, "/public/") || + req.URL.Path == "/robots.txt" || + req.URL.Path == "/favicon.ico" || + req.URL.Path == "/api/health" +} + +func TraceAllPaths(req *http.Request) bool { + return true +} + +func RequestTracing(tracer trace.Tracer, shouldTrace func(*http.Request) bool) web.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // skip tracing for a few endpoints - if strings.HasPrefix(req.URL.Path, "/public/") || - req.URL.Path == "/robots.txt" || - req.URL.Path == "/favicon.ico" || - req.URL.Path == "/api/health" { + if !shouldTrace(req) { next.ServeHTTP(w, req) return } diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index f2f2a420e22..61c8befdbba 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/plugins/codegen -go 1.24.4 +go 1.24.5 replace github.com/grafana/grafana/pkg/codegen => ../../codegen @@ -28,7 +28,7 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect @@ -45,7 +45,7 @@ require ( golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/tools v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index 1771a71bf9b..edb0ad11554 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -51,8 +51,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -98,8 +98,8 @@ golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= diff --git a/pkg/plugins/config/config.go b/pkg/plugins/config/config.go index 0a2fdce14f8..9e345e2c26c 100644 --- a/pkg/plugins/config/config.go +++ b/pkg/plugins/config/config.go @@ -30,11 +30,9 @@ type PluginManagementCfg struct { // Features contains the feature toggles used for the plugin management system. type Features struct { - ExternalCorePluginsEnabled bool - SkipHostEnvVarsEnabled bool - SriChecksEnabled bool - PluginsCDNSyncLoaderEnabled bool - LocalizationForPlugins bool + SkipHostEnvVarsEnabled bool + SriChecksEnabled bool + LocalizationForPlugins bool // Needed only until Tempo Alerting / metrics TraceQL is stable // https://github.com/grafana/grafana/issues/106888 TempoAlertingEnabled bool diff --git a/pkg/plugins/manager/loader/loader.go b/pkg/plugins/manager/loader/loader.go index f3c0d47e73b..e6bbc257f23 100644 --- a/pkg/plugins/manager/loader/loader.go +++ b/pkg/plugins/manager/loader/loader.go @@ -93,10 +93,8 @@ func (l *Loader) Load(ctx context.Context, src plugins.PluginSource) ([]*plugins } validateResults := make(chan validateResult, len(bootstrappedPlugins)) - // If the PluginsCDNSyncLoaderEnabled feature is enabled, validate plugins in parallel. - // Otherwise, validate plugins sequentially. var limitSize int - if l.cfg.Features.PluginsCDNSyncLoaderEnabled && src.PluginClass(ctx) == plugins.ClassCDN { + if src.PluginClass(ctx) == plugins.ClassCDN { limitSize = min(len(bootstrappedPlugins), concurrencyLimit) } else { limitSize = 1 diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 84813438862..3f16895acd4 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -1,19 +1,19 @@ module github.com/grafana/grafana/pkg/promlib -go 1.24.4 +go 1.24.5 require ( github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 github.com/grafana/grafana-plugin-sdk-go v0.278.0 github.com/json-iterator/go v1.1.12 github.com/prometheus/client_golang v1.22.0 - github.com/prometheus/common v0.64.0 + github.com/prometheus/common v0.65.0 github.com/prometheus/prometheus v0.303.1 github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 google.golang.org/protobuf v1.36.6 - k8s.io/apimachinery v0.33.2 + k8s.io/apimachinery v0.33.3 ) require ( @@ -33,12 +33,12 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/elazarl/goproxy v1.7.2 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -58,7 +58,7 @@ require ( github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -69,7 +69,7 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/magefile/mage v1.15.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -105,18 +105,21 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/tools v0.34.0 // indirect @@ -128,12 +131,12 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.2 // indirect + k8s.io/client-go v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index b114304cebd..f670db8ec6d 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -59,8 +59,8 @@ github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= @@ -75,8 +75,8 @@ github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7M github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= @@ -147,8 +147,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= @@ -190,8 +190,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -251,8 +251,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= @@ -318,36 +318,38 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -371,8 +373,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -430,22 +432,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/pkg/registry/apis/dashboard/large.go b/pkg/registry/apis/dashboard/large.go index c9c83f0dd37..21c9d5f8832 100644 --- a/pkg/registry/apis/dashboard/large.go +++ b/pkg/registry/apis/dashboard/large.go @@ -8,7 +8,8 @@ import ( dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" commonV0 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/apistore" @@ -35,8 +36,14 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme, threshold int) *apis reduceUnstructredSpec(&dash.Spec) case *dashboardV1.Dashboard: reduceUnstructredSpec(&dash.Spec) - case *dashboardV2.Dashboard: - dash.Spec = dashboardV2.DashboardSpec{ + case *dashboardV2alpha1.Dashboard: + dash.Spec = dashboardV2alpha1.DashboardSpec{ + Title: dash.Spec.Title, + Description: dash.Spec.Description, + Tags: dash.Spec.Tags, + } + case *dashboardV2alpha2.Dashboard: + dash.Spec = dashboardV2alpha2.DashboardSpec{ Title: dash.Spec.Title, Description: dash.Spec.Description, Tags: dash.Spec.Tags, @@ -55,7 +62,9 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme, threshold int) *apis return dash.Spec.UnmarshalJSON(blob) case *dashboardV1.Dashboard: return dash.Spec.UnmarshalJSON(blob) - case *dashboardV2.Dashboard: + case *dashboardV2alpha1.Dashboard: + return json.Unmarshal(blob, &dash.Spec) + case *dashboardV2alpha2.Dashboard: return json.Unmarshal(blob, &dash.Spec) default: return fmt.Errorf("unsupported dashboard type %T", obj) diff --git a/pkg/registry/apis/dashboard/large_test.go b/pkg/registry/apis/dashboard/large_test.go index cdeeb0310f8..fa62d9e62f6 100644 --- a/pkg/registry/apis/dashboard/large_test.go +++ b/pkg/registry/apis/dashboard/large_test.go @@ -11,7 +11,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashv2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" ) func TestLargeDashboardSupportV1(t *testing.T) { @@ -73,47 +74,47 @@ func TestLargeDashboardSupportV1(t *testing.T) { require.Len(t, panels, expectedPanelCount) } -func TestLargeDashboardSupportV2(t *testing.T) { +func TestLargeDashboardSupportV2alpha1(t *testing.T) { // Test RebuildSpec functionality specifically for v2 dashboards // This tests the json.Unmarshal(blob, &dash.Spec) path for structured specs // unlike v0/v1 which use the UnmarshalJSON path for unstructured specs // Create a v2 dashboard with structured spec - originalV2Dash := &dashv2.Dashboard{ + originalV2Dash := &dashv2alpha1.Dashboard{ ObjectMeta: metav1.ObjectMeta{ Name: "test-v2", Namespace: "test", }, - Spec: dashv2.DashboardSpec{ + Spec: dashv2alpha1.DashboardSpec{ Title: "Test V2 Dashboard", Description: stringPtr("A test dashboard for v2 large object support"), Tags: []string{"test", "v2", "large-object"}, Editable: boolPtr(true), LiveNow: boolPtr(false), Preload: false, - Annotations: []dashv2.DashboardAnnotationQueryKind{ + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{ { Kind: "AnnotationQuery", - Spec: dashv2.DashboardAnnotationQuerySpec{ + Spec: dashv2alpha1.DashboardAnnotationQuerySpec{ Name: "Test Annotation", }, }, }, - Elements: map[string]dashv2.DashboardElement{ + Elements: map[string]dashv2alpha1.DashboardElement{ "panel-1": { - PanelKind: &dashv2.DashboardPanelKind{}, + PanelKind: &dashv2alpha1.DashboardPanelKind{}, }, }, - Layout: dashv2.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{}, - TimeSettings: dashv2.DashboardTimeSettingsSpec{}, - CursorSync: dashv2.DashboardDashboardCursorSyncOff, - Variables: []dashv2.DashboardVariableKind{}, - Links: []dashv2.DashboardDashboardLink{}, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{}, + TimeSettings: dashv2alpha1.DashboardTimeSettingsSpec{}, + CursorSync: dashv2alpha1.DashboardDashboardCursorSyncOff, + Variables: []dashv2alpha1.DashboardVariableKind{}, + Links: []dashv2alpha1.DashboardDashboardLink{}, }, } scheme := runtime.NewScheme() - err := dashv2.AddToScheme(scheme) + err := dashv2alpha1.AddToScheme(scheme) require.NoError(t, err) largeObject := NewDashboardLargeObjectSupport(scheme, 0) @@ -142,7 +143,107 @@ func TestLargeDashboardSupportV2(t *testing.T) { require.Empty(t, dashToReduce.Spec.Links) // Now test RebuildSpec - this is the key test for v2! - rehydratedDash := &dashv2.Dashboard{ + rehydratedDash := &dashv2alpha1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-v2-rehydrated", + Namespace: "test", + }, + } + + // This tests the json.Unmarshal(blob, &dash.Spec) path for v2 dashboards + err = largeObject.RebuildSpec(rehydratedDash, originalSpecBlob) + require.NoError(t, err) + + // Verify the full dashboard spec is restored correctly + require.Equal(t, originalV2Dash.Spec.Title, rehydratedDash.Spec.Title) + require.Equal(t, originalV2Dash.Spec.Description, rehydratedDash.Spec.Description) + require.Equal(t, originalV2Dash.Spec.Tags, rehydratedDash.Spec.Tags) + require.Equal(t, originalV2Dash.Spec.Editable, rehydratedDash.Spec.Editable) + require.Equal(t, originalV2Dash.Spec.LiveNow, rehydratedDash.Spec.LiveNow) + require.Equal(t, originalV2Dash.Spec.Preload, rehydratedDash.Spec.Preload) + + // Verify annotations are restored + require.Len(t, rehydratedDash.Spec.Annotations, 1) + annotation := rehydratedDash.Spec.Annotations[0] + require.Equal(t, "AnnotationQuery", annotation.Kind) + require.Equal(t, "Test Annotation", annotation.Spec.Name) + + // Verify elements are restored + require.Len(t, rehydratedDash.Spec.Elements, 1) + _, exists := rehydratedDash.Spec.Elements["panel-1"] + require.True(t, exists) +} + +func TestLargeDashboardSupportV2alpha2(t *testing.T) { + // Test RebuildSpec functionality specifically for v2 dashboards + // This tests the json.Unmarshal(blob, &dash.Spec) path for structured specs + // unlike v0/v1 which use the UnmarshalJSON path for unstructured specs + + // Create a v2 dashboard with structured spec + originalV2Dash := &dashv2alpha2.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-v2", + Namespace: "test", + }, + Spec: dashv2alpha2.DashboardSpec{ + Title: "Test V2 Dashboard", + Description: stringPtr("A test dashboard for v2 large object support"), + Tags: []string{"test", "v2", "large-object"}, + Editable: boolPtr(true), + LiveNow: boolPtr(false), + Preload: false, + Annotations: []dashv2alpha2.DashboardAnnotationQueryKind{ + { + Kind: "AnnotationQuery", + Spec: dashv2alpha2.DashboardAnnotationQuerySpec{ + Name: "Test Annotation", + }, + }, + }, + Elements: map[string]dashv2alpha2.DashboardElement{ + "panel-1": { + PanelKind: &dashv2alpha2.DashboardPanelKind{}, + }, + }, + Layout: dashv2alpha2.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{}, + TimeSettings: dashv2alpha2.DashboardTimeSettingsSpec{}, + CursorSync: dashv2alpha2.DashboardDashboardCursorSyncOff, + Variables: []dashv2alpha2.DashboardVariableKind{}, + Links: []dashv2alpha2.DashboardDashboardLink{}, + }, + } + + scheme := runtime.NewScheme() + err := dashv2alpha2.AddToScheme(scheme) + require.NoError(t, err) + + largeObject := NewDashboardLargeObjectSupport(scheme, 0) + + // Marshal the original spec to use as our "blob" data + originalSpecBlob, err := json.Marshal(originalV2Dash.Spec) + require.NoError(t, err) + + // Create a copy to test reduction + dashToReduce := originalV2Dash.DeepCopy() + + // Convert the dashboard to a small value (ReduceSpec) + err = largeObject.ReduceSpec(dashToReduce) + require.NoError(t, err) + + // Verify only essential fields remain after reduction + require.Equal(t, "Test V2 Dashboard", dashToReduce.Spec.Title) + require.Equal(t, stringPtr("A test dashboard for v2 large object support"), dashToReduce.Spec.Description) + require.Equal(t, []string{"test", "v2", "large-object"}, dashToReduce.Spec.Tags) + + // Everything else should be empty/default + require.Empty(t, dashToReduce.Spec.Annotations) + require.Empty(t, dashToReduce.Spec.Elements) + require.Nil(t, dashToReduce.Spec.Layout.GridLayoutKind) + require.Empty(t, dashToReduce.Spec.Variables) + require.Empty(t, dashToReduce.Spec.Links) + + // Now test RebuildSpec - this is the key test for v2! + rehydratedDash := &dashv2alpha2.Dashboard{ ObjectMeta: metav1.ObjectMeta{ Name: "test-v2-rehydrated", Namespace: "test", diff --git a/pkg/registry/apis/dashboard/legacy/migrate.go b/pkg/registry/apis/dashboard/legacy/migrate.go index 5692b490e95..bb938f7e532 100644 --- a/pkg/registry/apis/dashboard/legacy/migrate.go +++ b/pkg/registry/apis/dashboard/legacy/migrate.go @@ -211,10 +211,11 @@ func (a *dashboardSqlAccess) countValues(ctx context.Context, opts MigrateOption func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) { query := &DashboardQuery{ - OrgID: orgId, - Limit: 100000000, - GetHistory: opts.WithHistory, // include history - Order: "ASC", // oldest first + OrgID: orgId, + Limit: 100000000, + GetHistory: opts.WithHistory, // include history + AllowFallback: true, // allow fallback to dashboard table during migration + Order: "ASC", // oldest first } blobs := &BlobStoreInfo{} @@ -309,6 +310,7 @@ func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, "dashboard", row.Dash.Name, "uid", row.Dash.UID, "id", id, + "namespace", opts.Namespace, ) opts.Progress(-2, fmt.Sprintf("rejected: id:%s, uid:%s", id, row.Dash.Name)) } diff --git a/pkg/registry/apis/dashboard/legacy/migrate_test.go b/pkg/registry/apis/dashboard/legacy/migrate_test.go new file mode 100644 index 00000000000..5b134cb31dc --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/migrate_test.go @@ -0,0 +1,154 @@ +package legacy + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/storage/legacysql" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks" +) + +func TestDashboardMigrationQuery(t *testing.T) { + // Test that migration queries use AllowFallback flag correctly + nodb := &legacysql.LegacyDatabaseHelper{ + Table: func(n string) string { + return "grafana." + n + }, + } + + t.Run("Migration query should enable AllowFallback flag", func(t *testing.T) { + // Create a migration query as would be used in actual migration + migrationQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, // Migration includes history + AllowFallback: true, // This is the key flag for migration + Order: "ASC", // Migration uses ascending order + } + + // Verify UseHistoryTable returns true (requirement for COALESCE logic) + require.True(t, migrationQuery.UseHistoryTable(), "Migration query should use history table") + + // Verify the flag is set correctly + require.True(t, migrationQuery.AllowFallback, "Migration query should allow fallback") + require.True(t, migrationQuery.GetHistory, "Migration query should get history") + require.Equal(t, "ASC", migrationQuery.Order, "Migration should use ascending order") + }) + + t.Run("Regular history query should not use AllowFallback", func(t *testing.T) { + // Regular history query without migration + historyQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + Order: "DESC", + } + + require.True(t, historyQuery.UseHistoryTable(), "History query should use history table") + require.False(t, historyQuery.AllowFallback, "Regular history query should not allow fallback") + require.True(t, historyQuery.GetHistory, "History query should get history") + }) + + t.Run("Migration query template produces COALESCE SQL", func(t *testing.T) { + // Test that the SQL template produces COALESCE logic for migration queries + migrationQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + AllowFallback: true, + Order: "ASC", + } + + req := newQueryReq(nodb, migrationQuery) + req.SQLTemplate = mocks.NewTestingSQLTemplate() + + // Execute the template to get the generated SQL + rawQuery, err := sqltemplate.Execute(sqlQueryDashboards, &req) + require.NoError(t, err) + + sql := rawQuery + + // Verify that COALESCE functions are present in the generated SQL + // These should be used when GetHistory=true AND AllowFallback=true + require.Contains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated)", + "Migration SQL should contain COALESCE for updated timestamp") + require.Contains(t, sql, "COALESCE(dashboard_version.version, dashboard.version)", + "Migration SQL should contain COALESCE for version") + require.Contains(t, sql, "COALESCE(dashboard_version.data, dashboard.data)", + "Migration SQL should contain COALESCE for data") + require.Contains(t, sql, "COALESCE(dashboard_version.api_version, dashboard.api_version)", + "Migration SQL should contain COALESCE for api_version") + require.Contains(t, sql, "COALESCE(dashboard_version.message, '')", + "Migration SQL should contain COALESCE for message with empty string fallback") + + // Verify ORDER BY uses COALESCE as well + require.Contains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated) ASC", + "Migration SQL should ORDER BY COALESCED created timestamp") + require.Contains(t, sql, "COALESCE(dashboard_version.version, dashboard.version) ASC", + "Migration SQL should ORDER BY COALESCED version") + + // Verify it doesn't have the strict history table filter that would exclude NULL version entries + require.NotContains(t, sql, "dashboard_version.id IS NOT NULL", + "Migration SQL should not exclude dashboards without version entries") + }) + + t.Run("Regular history query produces strict SQL", func(t *testing.T) { + // Test that regular history queries still use strict dashboard_version fields + historyQuery := &DashboardQuery{ + OrgID: 1, + GetHistory: true, + Order: "DESC", + } + + req := newQueryReq(nodb, historyQuery) + req.SQLTemplate = mocks.NewTestingSQLTemplate() + + rawQuery, err := sqltemplate.Execute(sqlQueryDashboards, &req) + require.NoError(t, err) + + sql := rawQuery + + // Verify that direct dashboard_version fields are used (no COALESCE) + require.Contains(t, sql, "dashboard_version.created as updated", + "Regular history SQL should use direct dashboard_version.created") + require.Contains(t, sql, "dashboard_version.version", + "Regular history SQL should use direct dashboard_version.version") + require.Contains(t, sql, "dashboard_version.data", + "Regular history SQL should use direct dashboard_version.data") + + // NOTE: We intentionally do NOT add dashboard_version.id IS NOT NULL filter + // to allow for cases where dashboard_version entries might be missing + + // Should not contain COALESCE functions + require.NotContains(t, sql, "COALESCE(dashboard_version.created, dashboard.updated)", + "Regular history SQL should not contain COALESCE for updated") + }) +} + +func TestMigrateDashboardsConfiguration(t *testing.T) { + // Test the actual migration function configuration + + t.Run("Migration options should configure query correctly", func(t *testing.T) { + // Test the migration configuration as used in real migration + opts := MigrateOptions{ + WithHistory: true, // Migration includes history + } + + // This simulates what happens in migrateDashboards function + expectedQuery := &DashboardQuery{ + OrgID: 1, + Limit: 100000000, + GetHistory: opts.WithHistory, // Should be true + AllowFallback: true, // Should be true for migration + Order: "ASC", // Should be ASC for migration + } + + // Verify the configuration matches what migration sets up + require.True(t, expectedQuery.GetHistory, "Migration should enable GetHistory") + require.True(t, expectedQuery.AllowFallback, "Migration should enable AllowFallback") + require.Equal(t, "ASC", expectedQuery.Order, "Migration should use ascending order") + require.Equal(t, 100000000, expectedQuery.Limit, "Migration should use large limit") + + // Verify UseHistoryTable logic + require.True(t, expectedQuery.UseHistoryTable(), "Migration query should use history table") + }) +} diff --git a/pkg/registry/apis/dashboard/legacy/queries_test.go b/pkg/registry/apis/dashboard/legacy/queries_test.go index d2331267177..3ee82e899d7 100644 --- a/pkg/registry/apis/dashboard/legacy/queries_test.go +++ b/pkg/registry/apis/dashboard/legacy/queries_test.go @@ -85,6 +85,15 @@ func TestDashboardQueries(t *testing.T) { Order: "ASC", }), }, + { + Name: "migration_with_fallback", + Data: getQuery(&DashboardQuery{ + OrgID: 1, + GetHistory: true, + AllowFallback: true, + Order: "ASC", + }), + }, }, sqlQueryPanels: { { diff --git a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql index d328be4e805..75439c60d03 100644 --- a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql +++ b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql @@ -13,14 +13,24 @@ SELECT created_user.uid as created_by, dashboard.created_by as created_by_id, {{ if .Query.UseHistoryTable }} - dashboard_version.created, + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, dashboard_version.api_version {{ else }} + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version + {{ end }} + {{ else }} dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id, @@ -35,7 +45,7 @@ LEFT OUTER JOIN {{ .Ident .VersionTable }} as dashboard_version ON dashboard.id {{ end }} LEFT OUTER JOIN {{ .Ident .ProvisioningTable }} as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN {{ .Ident .UserTable }} as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN {{ .Ident .UserTable }} as updated_user ON {{ if .Query.UseHistoryTable }}dashboard_version.created_by = updated_user.id{{ else }}dashboard.updated_by = updated_user.id{{ end }} +LEFT OUTER JOIN {{ .Ident .UserTable }} as updated_user ON {{ if .Query.UseHistoryTable }}{{ if and .Query.GetHistory (not .Query.AllowFallback) }}dashboard_version.created_by = updated_user.id{{ else }}COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id{{ end }}{{ else }}dashboard.updated_by = updated_user.id{{ end }} WHERE dashboard.is_folder = {{ .Arg .Query.GetFolders }} AND dashboard.org_id = {{ .Arg .Query.OrgID }} {{ if .Query.UseHistoryTable }} @@ -45,11 +55,20 @@ WHERE dashboard.is_folder = {{ .Arg .Query.GetFolders }} {{ if .Query.Version }} AND dashboard_version.version = {{ .Arg .Query.Version }} {{ else if .Query.LastID }} + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} AND dashboard_version.version < {{ .Arg .Query.LastID }} + {{ else }} + AND COALESCE(dashboard_version.version, dashboard.version) < {{ .Arg .Query.LastID }} + {{ end }} {{ end }} ORDER BY + {{ if and .Query.GetHistory (not .Query.AllowFallback) }} dashboard_version.created {{ .Query.Order }}, dashboard_version.version {{ .Query.Order }}, + {{ else }} + COALESCE(dashboard_version.created, dashboard.updated) {{ .Query.Order }}, + COALESCE(dashboard_version.version, dashboard.version) {{ .Query.Order }}, + {{ end }} dashboard.uid ASC {{ else }} {{ if .Query.UID }} diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 1c14928daeb..0771f0fc3ee 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -175,7 +175,8 @@ func (r *rowsWrapper) Next() bool { r.row, err = r.a.scanRow(r.rows, r.history) if err != nil { - if len(r.rejected) > 1000 || r.row == nil { + r.a.log.Error("error scanning dashboard", "error", err) + if len(r.rejected) > 0 || r.row == nil { r.err = fmt.Errorf("too many rejected rows (%d) %w", len(r.rejected), err) return false } @@ -263,6 +264,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo &updated, &updatedBy, &updatedByID, &version, &message, &data, &apiVersion, ) + switch apiVersion.String { case "": apiVersion.String = dashboardV0.VERSION // default value diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index 626d022d39d..41da615b6ff 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -128,6 +128,65 @@ func TestScanRow(t *testing.T) { require.Equal(t, "slo", manager.Identity) // the ID of the plugin require.Equal(t, "", meta.GetAnnotations()[utils.AnnoKeySourceChecksum]) // hash is not used on plugins }) + + t.Run("Migration scenario should use COALESCE logic with AllowFallback=true", func(t *testing.T) { + // This specifically tests the migration use case where GetHistory=true but AllowFallback=true + // allows the query to use COALESCE logic to fall back to dashboard table data when + // dashboard_version entries are missing. + + migrationTimestamp := timestamp.Add(2 * time.Hour) // Migration scenario timestamp + migrationVersion := int64(5) + migrationUpdatedUser := "migration_updater" + migrationMessage := "" // Empty message for migration (COALESCE behavior) + migrationData := []byte(`{"migration": "data", "title": "Migrated Dashboard"}`) + migrationAPIVersion := "v0alpha1" + + // In migration scenario, COALESCE functions return dashboard table values + // when dashboard_version values are NULL, ensuring all dashboards are migrated + rows := sqlmock.NewRows(columns).AddRow( + 1, id, title, folderUID, nil, "", // basic dashboard fields + "", "", "", 0, // origin fields + timestamp, createdUser, 0, // created fields + // These represent COALESCED values from dashboard table (not version table) + migrationTimestamp, migrationUpdatedUser, 0, migrationVersion, migrationMessage, migrationData, migrationAPIVersion, + ) + + mock.ExpectQuery("SELECT *").WillReturnRows(rows) + resultRows, err := mockDB.Query("SELECT *") + require.NoError(t, err) + defer resultRows.Close() // nolint:errcheck + resultRows.Next() + + // Test with history=true (migration scenario) - should work with COALESCED values + row, err := store.scanRow(resultRows, true) + require.NoError(t, err) + require.NotNil(t, row) + + // Verify migration scenario works correctly with fallback data + require.Equal(t, title, row.Dash.Name) + require.Equal(t, migrationVersion, row.RV) // Should use COALESCEd dashboard table version + require.Equal(t, common.Unstructured{ + Object: map[string]interface{}{ + "migration": "data", + "title": "Migrated Dashboard", + }, + }, row.Dash.Spec) // Should use COALESCEd dashboard table data + require.Equal(t, "default", row.Dash.Namespace) + + // Token should use the version in history mode (migration scenario) + require.Equal(t, &continueToken{orgId: int64(1), id: migrationVersion}, row.token) + + meta, err := utils.MetaAccessor(row.Dash) + require.NoError(t, err) + require.Equal(t, id, meta.GetDeprecatedInternalID()) // nolint:staticcheck + require.Equal(t, migrationVersion, meta.GetGeneration()) // Should use COALESCEd version + require.Equal(t, k8sTimestamp, meta.GetCreationTimestamp()) // Created timestamp unchanged + require.Equal(t, "user:"+createdUser, meta.GetCreatedBy()) // Original creator preserved + require.Equal(t, "user:"+migrationUpdatedUser, meta.GetUpdatedBy()) // COALESCEd updater + require.Equal(t, migrationMessage, meta.GetMessage()) // Empty message from COALESCE + require.Equal(t, folderUID, meta.GetFolder()) + require.Equal(t, "dashboard.grafana.app/"+migrationAPIVersion, row.Dash.APIVersion) + }) } func TestBuildSaveDashboardCommand(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql index e425f56c9c7..fd54e6dc949 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql index e0c9df4fae1..e04b1ff7430 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM `grafana`.`dashboard` as dashboard LEFT OUTER JOIN `grafana`.`dashboard_version` as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN `grafana`.`dashboard_provisioning` as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN `grafana`.`user` as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN `grafana`.`user` as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN `grafana`.`user` as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..ba38c048136 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM `grafana`.`dashboard` as dashboard +LEFT OUTER JOIN `grafana`.`dashboard_version` as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN `grafana`.`dashboard_provisioning` as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN `grafana`.`user` as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN `grafana`.`user` as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql index 16ddeda410f..4d0affb337f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql index 07702d215a4..61311c5c99b 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM "grafana"."dashboard" as dashboard LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN "grafana"."user" as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..364fbebf417 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM "grafana"."dashboard" as dashboard +LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql index 16ddeda410f..4d0affb337f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql @@ -12,9 +12,9 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + dashboard_version.created as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, + dashboard_version.created_by as updated_by_id, dashboard_version.version, dashboard_version.message, dashboard_version.data, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql index 07702d215a4..61311c5c99b 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql @@ -12,23 +12,23 @@ SELECT dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id, - dashboard_version.created, + COALESCE(dashboard_version.created, dashboard.updated) as updated, updated_user.uid as updated_by, - updated_user.id as created_by_id, - dashboard_version.version, - dashboard_version.message, - dashboard_version.data, - dashboard_version.api_version + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version FROM "grafana"."dashboard" as dashboard LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id -LEFT OUTER JOIN "grafana"."user" as updated_user ON dashboard_version.created_by = updated_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id WHERE dashboard.is_folder = FALSE AND dashboard.org_id = 2 AND dashboard.uid = 'UUU' AND dashboard_version.version = 3 ORDER BY - dashboard_version.created DESC, - dashboard_version.version DESC, + COALESCE(dashboard_version.created, dashboard.updated) DESC, + COALESCE(dashboard_version.version, dashboard.version) DESC, dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql new file mode 100755 index 00000000000..364fbebf417 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql @@ -0,0 +1,32 @@ +SELECT + dashboard.org_id, + dashboard.id, + dashboard.uid, + dashboard.folder_uid, + dashboard.deleted, + plugin_id, + provisioning.name as repo_name, + provisioning.external_id as repo_path, + provisioning.check_sum as repo_hash, + provisioning.updated as repo_ts, + dashboard.created, + created_user.uid as created_by, + dashboard.created_by as created_by_id, + COALESCE(dashboard_version.created, dashboard.updated) as updated, + updated_user.uid as updated_by, + COALESCE(dashboard_version.created_by, dashboard.updated_by) as updated_by_id, + COALESCE(dashboard_version.version, dashboard.version) as version, + COALESCE(dashboard_version.message, '') as message, + COALESCE(dashboard_version.data, dashboard.data) as data, + COALESCE(dashboard_version.api_version, dashboard.api_version) as api_version +FROM "grafana"."dashboard" as dashboard +LEFT OUTER JOIN "grafana"."dashboard_version" as dashboard_version ON dashboard.id = dashboard_version.dashboard_id +LEFT OUTER JOIN "grafana"."dashboard_provisioning" as provisioning ON dashboard.id = provisioning.dashboard_id +LEFT OUTER JOIN "grafana"."user" as created_user ON dashboard.created_by = created_user.id +LEFT OUTER JOIN "grafana"."user" as updated_user ON COALESCE(dashboard_version.created_by, dashboard.updated_by) = updated_user.id +WHERE dashboard.is_folder = FALSE + AND dashboard.org_id = 1 + ORDER BY + COALESCE(dashboard_version.created, dashboard.updated) ASC, + COALESCE(dashboard_version.version, dashboard.version) ASC, + dashboard.uid ASC diff --git a/pkg/registry/apis/dashboard/legacy/types.go b/pkg/registry/apis/dashboard/legacy/types.go index f20f9232afb..4ffa7b80863 100644 --- a/pkg/registry/apis/dashboard/legacy/types.go +++ b/pkg/registry/apis/dashboard/legacy/types.go @@ -27,6 +27,10 @@ type DashboardQuery struct { GetHistory bool Version int64 + // Allow fallback to dashboard table when version data is missing + // Used during migration to handle dashboards without version entries + AllowFallback bool + // Only folders GetFolders bool diff --git a/pkg/registry/apis/dashboard/mutate.go b/pkg/registry/apis/dashboard/mutate.go index 2470d35b417..574cf5ea634 100644 --- a/pkg/registry/apis/dashboard/mutate.go +++ b/pkg/registry/apis/dashboard/mutate.go @@ -11,7 +11,8 @@ import ( dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" "github.com/grafana/grafana/apps/dashboard/pkg/migration" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -57,17 +58,30 @@ func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attribute Error: migrationErr.Error(), } } - case *dashboardV2.Dashboard: + + case *dashboardV2alpha1.Dashboard: // Temporary fix: The generator fails to properly initialize this property, so we'll do it here // until the generator is fixed. if v.Spec.Layout.GridLayoutKind == nil && v.Spec.Layout.RowsLayoutKind == nil && v.Spec.Layout.AutoGridLayoutKind == nil && v.Spec.Layout.TabsLayoutKind == nil { - v.Spec.Layout.GridLayoutKind = &dashboardV2.DashboardGridLayoutKind{ + v.Spec.Layout.GridLayoutKind = &dashboardV2alpha1.DashboardGridLayoutKind{ Kind: "GridLayout", - Spec: dashboardV2.DashboardGridLayoutSpec{}, + Spec: dashboardV2alpha1.DashboardGridLayoutSpec{}, } } - resourceInfo = dashboardV2.DashboardResourceInfo + resourceInfo = dashboardV2alpha1.DashboardResourceInfo + + case *dashboardV2alpha2.Dashboard: + // Temporary fix: The generator fails to properly initialize this property, so we'll do it here + // until the generator is fixed. + if v.Spec.Layout.GridLayoutKind == nil && v.Spec.Layout.RowsLayoutKind == nil && v.Spec.Layout.AutoGridLayoutKind == nil && v.Spec.Layout.TabsLayoutKind == nil { + v.Spec.Layout.GridLayoutKind = &dashboardV2alpha2.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashboardV2alpha2.DashboardGridLayoutSpec{}, + } + } + + resourceInfo = dashboardV2alpha2.DashboardResourceInfo // Noop for V2 default: diff --git a/pkg/registry/apis/dashboard/mutation_test.go b/pkg/registry/apis/dashboard/mutation_test.go index 63ddefd236d..bfbbb7f0109 100644 --- a/pkg/registry/apis/dashboard/mutation_test.go +++ b/pkg/registry/apis/dashboard/mutation_test.go @@ -12,7 +12,8 @@ import ( dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashv2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" "github.com/grafana/grafana/apps/dashboard/pkg/migration" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" @@ -141,9 +142,19 @@ func TestDashboardAPIBuilder_Mutate(t *testing.T) { expectedError: false, }, { - name: "v2 should set layout if it is not set", - inputObj: &v2alpha1.Dashboard{ - Spec: v2alpha1.DashboardSpec{ + name: "v2alpha1 should set layout if it is not set", + inputObj: &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Title: "test123", + }, + }, + operation: admission.Create, + expectedTitle: "test123", + }, + { + name: "v2alpha2 should set layout if it is not set", + inputObj: &dashv2alpha2.Dashboard{ + Spec: dashv2alpha2.DashboardSpec{ Title: "test123", }, }, @@ -203,7 +214,8 @@ func TestDashboardAPIBuilder_Mutate(t *testing.T) { if tt.migrationExpected { require.Equal(t, schemaversion.LATEST_VERSION, schemaVersion, "dashboard should be migrated to the latest version") } - case *v2alpha1.Dashboard: + case *dashv2alpha1.Dashboard: + case *dashv2alpha2.Dashboard: require.Equal(t, tt.expectedTitle, v.Spec.Title, "title should be set") require.NotNil(t, v.Spec.Layout, "layout should be set") require.NotNil(t, v.Spec.Layout.GridLayoutKind, "layout should be a GridLayout") diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index f2fda01d1c9..8bc223b79f8 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -122,7 +122,7 @@ func RegisterAPIService( dbp := legacysql.NewDatabaseProvider(sql) namespacer := request.GetNamespaceMapper(cfg) legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) - folderClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), folders.FolderResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashStore, userService, unified, sorter) + folderClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), folders.FolderResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashStore, userService, unified, sorter, features) builder := &DashboardsAPIBuilder{ log: log.New("grafana-apiserver.dashboards"), diff --git a/pkg/registry/apis/dashboard/schema_validation.go b/pkg/registry/apis/dashboard/schema_validation.go index 26749eaa598..91629e5eeb5 100644 --- a/pkg/registry/apis/dashboard/schema_validation.go +++ b/pkg/registry/apis/dashboard/schema_validation.go @@ -12,7 +12,8 @@ import ( v0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" v1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - v2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + v2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + v2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/featuremgmt" ) @@ -32,7 +33,8 @@ func (b *DashboardsAPIBuilder) ValidateDashboardSpec(ctx context.Context, obj ru errorOnSchemaMismatches = false // Never error for v0 case *v1.Dashboard: errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV1) - case *v2.Dashboard: + case *v2alpha1.Dashboard: + case *v2alpha2.Dashboard: errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV2) default: return nil, fmt.Errorf("invalid dashboard type: %T", obj) @@ -52,8 +54,10 @@ func (b *DashboardsAPIBuilder) ValidateDashboardSpec(ctx context.Context, obj ru errors, schemaVersionError = v0.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) case *v1.Dashboard: errors, schemaVersionError = v1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) - case *v2.Dashboard: - errors = v2.ValidateDashboardSpec(v) + case *v2alpha1.Dashboard: + errors = v2alpha1.ValidateDashboardSpec(v) + case *v2alpha2.Dashboard: + errors = v2alpha2.ValidateDashboardSpec(v) } } diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 17643ce2743..4252502e9ef 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -43,7 +43,7 @@ type SearchHandler struct { } func NewSearchHandler(tracer trace.Tracer, dual dualwrite.Service, legacyDashboardSearcher resourcepb.ResourceIndexClient, resourceClient resource.ResourceClient, features featuremgmt.FeatureToggles) *SearchHandler { - searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), dashboardv0alpha1.DashboardResourceInfo.GroupResource(), resourceClient, legacyDashboardSearcher) + searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), dashboardv0alpha1.DashboardResourceInfo.GroupResource(), resourceClient, legacyDashboardSearcher, features) return &SearchHandler{ client: searchClient, log: log.New("grafana-apiserver.dashboards.search"), diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index 64c4b40655c..d4c96edf03a 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -43,4 +43,10 @@ type IdentityAccessManagementAPIBuilder struct { // Toggle for enabling authz management apis enableAuthZApis bool + + // Toggle for enabling authn mutation + enableAuthnMutation bool + + // Toggle for enabling dual writer + enableDualWriter bool } diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index da3517c82f9..22d15d2e518 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -56,15 +56,17 @@ func RegisterAPIService( authorizer := newIAMAuthorizer(accessClient, legacyAccessClient) builder := &IdentityAccessManagementAPIBuilder{ - store: store, - coreRolesStorage: coreRolesStorage, - sso: ssoService, - authorizer: authorizer, - legacyAccessClient: legacyAccessClient, - accessClient: accessClient, - display: user.NewLegacyDisplayREST(store), - reg: reg, - enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis), + store: store, + coreRolesStorage: coreRolesStorage, + sso: ssoService, + authorizer: authorizer, + legacyAccessClient: legacyAccessClient, + accessClient: accessClient, + display: user.NewLegacyDisplayREST(store), + reg: reg, + enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis), + enableAuthnMutation: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation), + enableDualWriter: true, } apiregistration.RegisterAPI(builder) @@ -126,21 +128,24 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge storage[teamBindingResource.StoragePath()] = team.NewLegacyBindingStore(b.store) userResource := legacyiamv0.UserResourceInfo - store, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter) - if err != nil { - return err + legacyStore := user.NewLegacyStore(b.store, b.legacyAccessClient, b.enableAuthnMutation) + storage[userResource.StoragePath()] = legacyStore + + if b.enableDualWriter { + store, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter) + if err != nil { + return err + } + + dw, err := opts.DualWriteBuilder(userResource.GroupResource(), legacyStore, store) + if err != nil { + return err + } + + storage[userResource.StoragePath()] = dw } - legacyStore := user.NewLegacyStore(b.store, b.legacyAccessClient) - - dw, err := opts.DualWriteBuilder(userResource.GroupResource(), legacyStore, store) - if err != nil { - return err - } - - storage[userResource.StoragePath()] = dw storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.store) - serviceAccountResource := legacyiamv0.ServiceAccountResourceInfo storage[serviceAccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.store, b.legacyAccessClient) storage[serviceAccountResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store) diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index bb4f0458486..c211ece5001 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -37,18 +37,19 @@ var ( var resource = iamv0.UserResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { - return &LegacyStore{store, ac} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool) *LegacyStore { + return &LegacyStore{store, ac, enableAuthnMutation} } type LegacyStore struct { - store legacy.LegacyIdentityStore - ac claims.AccessClient + store legacy.LegacyIdentityStore + ac claims.AccessClient + enableAuthnMutation bool } // Update implements rest.Updater. func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return nil, false, fmt.Errorf("method not yet implemented") + return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") } // DeleteCollection implements rest.CollectionDeleter. @@ -58,6 +59,10 @@ func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation res // Delete implements rest.GracefulDeleter. func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + if !s.enableAuthnMutation { + return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") + } + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, false, err @@ -178,6 +183,10 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO // Create implements rest.Creater. func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + if !s.enableAuthnMutation { + return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") + } + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err diff --git a/pkg/registry/apis/ofrep/register.go b/pkg/registry/apis/ofrep/register.go index de3f5ab2702..154e3f6b6fc 100644 --- a/pkg/registry/apis/ofrep/register.go +++ b/pkg/registry/apis/ofrep/register.go @@ -61,9 +61,12 @@ func NewAPIBuilder(providerType string, url *url.URL, insecure bool, caFile stri } func RegisterAPIService(apiregistration builder.APIRegistrar, cfg *setting.Cfg) (*APIBuilder, error) { + if !cfg.OpenFeature.APIEnabled { + return nil, nil + } + var staticEvaluator featuremgmt.StaticFlagEvaluator // No static evaluator needed for non-static provider var err error - if cfg.OpenFeature.ProviderType == setting.StaticProviderType { staticEvaluator, err = featuremgmt.CreateStaticEvaluator(cfg) if err != nil { @@ -319,7 +322,7 @@ func (b *APIBuilder) isAuthenticatedRequest(r *http.Request) bool { if !ok { return false } - return user.GetIdentityType() != "" + return user.GetIdentityType() != types.TypeUnauthenticated } // validateNamespace checks if the namespace in the evaluation context matches the namespace in the request diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index a0548e1055b..4dd6604c989 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -93,9 +93,10 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime. return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() opts := resources.DualWriteOptions{ - Ref: query.Get("ref"), - Message: query.Get("message"), - SkipDryRun: query.Get("skipDryRun") == "true", + Ref: query.Get("ref"), + Message: query.Get("message"), + SkipDryRun: query.Get("skipDryRun") == "true", + OriginalPath: query.Get("originalPath"), } logger := logger.With("url", r.URL.Path, "ref", opts.Ref, "message", opts.Message) ctx := logging.Context(r.Context(), logger) @@ -139,7 +140,24 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime. } obj = resource.AsResourceWrapper() case http.MethodPost: - if isDir { + // Check if this is a move operation first (originalPath query parameter is present) + if opts.OriginalPath != "" { + // For move operations, only read body for file moves (not directory moves) + if !isDir { + opts.Data, err = readBody(r, filesMaxBodySize) + if err != nil { + responder.Error(err) + return + } + } + + resource, err := dualReadWriter.MoveResource(ctx, opts) + if err != nil { + responder.Error(err) + return + } + obj = resource.AsResourceWrapper() + } else if isDir { obj, err = dualReadWriter.CreateFolder(ctx, opts) } else { opts.Data, err = readBody(r, filesMaxBodySize) @@ -148,7 +166,8 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime. return } - resource, err := dualReadWriter.CreateResource(ctx, opts) + var resource *resources.ParsedResource + resource, err = dualReadWriter.CreateResource(ctx, opts) if err != nil { responder.Error(err) return diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker.go b/pkg/registry/apis/provisioning/jobs/delete/worker.go new file mode 100644 index 00000000000..5489445d216 --- /dev/null +++ b/pkg/registry/apis/provisioning/jobs/delete/worker.go @@ -0,0 +1,96 @@ +package delete + +import ( + "context" + "errors" + "fmt" + "time" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" +) + +type Worker struct { + syncWorker jobs.Worker + wrapFn repository.WrapWithStageFn +} + +func NewWorker(syncWorker jobs.Worker, wrapFn repository.WrapWithStageFn) *Worker { + return &Worker{ + syncWorker: syncWorker, + wrapFn: wrapFn, + } +} + +func (w *Worker) IsSupported(ctx context.Context, job provisioning.Job) bool { + return job.Spec.Action == provisioning.JobActionDelete +} + +func (w *Worker) Process(ctx context.Context, repo repository.Repository, job provisioning.Job, progress jobs.JobProgressRecorder) error { + if job.Spec.Delete == nil { + return errors.New("missing delete settings") + } + opts := *job.Spec.Delete + + paths := opts.Paths + progress.SetTotal(ctx, len(paths)) + progress.StrictMaxErrors(1) // Fail fast on any error during deletion + + fn := func(repo repository.Repository, _ bool) error { + rw, ok := repo.(repository.ReaderWriter) + if !ok { + return errors.New("delete job submitted targeting repository that is not a ReaderWriter") + } + + return w.deleteFiles(ctx, rw, progress, opts, paths...) + } + + stageOptions := repository.StageOptions{ + PushOnWrites: false, + Timeout: 10 * time.Minute, + } + + err := w.wrapFn(ctx, repo, stageOptions, fn) + if err != nil { + return fmt.Errorf("delete files from repository: %w", err) + } + + if opts.Ref == "" { + progress.ResetResults() + progress.SetMessage(ctx, "pull resources") + + syncJob := provisioning.Job{ + Spec: provisioning.JobSpec{ + Pull: &provisioning.SyncJobOptions{ + // Full sync because it's the only one that supports empty folder deletion + Incremental: false, + }, + }, + } + + if err := w.syncWorker.Process(ctx, repo, syncJob, progress); err != nil { + return fmt.Errorf("pull resources: %w", err) + } + } + + return nil +} + +func (w *Worker) deleteFiles(ctx context.Context, rw repository.ReaderWriter, progress jobs.JobProgressRecorder, opts provisioning.DeleteJobOptions, paths ...string) error { + for _, path := range paths { + result := jobs.JobResourceResult{ + Path: path, + Action: repository.FileActionDeleted, + } + + progress.SetMessage(ctx, "Deleting "+path) + result.Error = rw.Delete(ctx, path, opts.Ref, "Delete "+path) + progress.Record(ctx, result) + if err := progress.TooManyErrors(); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker_test.go b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go new file mode 100644 index 00000000000..d2ba09ba85d --- /dev/null +++ b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go @@ -0,0 +1,373 @@ +package delete + +import ( + "context" + "errors" + "testing" + "time" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type mockReaderWriter struct { + *repository.MockRepository +} + +func (m *mockReaderWriter) Delete(ctx context.Context, path, ref, message string) error { + args := m.Called(ctx, path, ref, message) + return args.Error(0) +} + +func TestDeleteWorker_IsSupported(t *testing.T) { + tests := []struct { + name string + job provisioning.Job + expected bool + }{ + { + name: "delete action is supported", + job: provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + }, + }, + expected: true, + }, + { + name: "pull action is not supported", + job: provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPull, + }, + }, + expected: false, + }, + { + name: "push action is not supported", + job: provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionPush, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + worker := NewWorker(nil, nil) + result := worker.IsSupported(context.Background(), tt.job) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestDeleteWorker_ProcessMissingDeleteSettings(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + }, + } + + worker := NewWorker(nil, nil) + err := worker.Process(context.Background(), nil, job, nil) + require.EqualError(t, err, "missing delete settings") +} + +func TestDeleteWorker_ProcessNotReaderWriter(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path"}, + }, + }, + } + + mockRepo := repository.NewMockRepository(t) + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool { + return !opts.PushOnWrites && opts.Timeout == 10*time.Minute + }), mock.Anything).Return(errors.New("delete job submitted targeting repository that is not a ReaderWriter")) + + mockProgress.On("SetTotal", mock.Anything, 1).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + + worker := NewWorker(nil, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.EqualError(t, err, "delete files from repository: delete job submitted targeting repository that is not a ReaderWriter") +} + +func TestDeleteWorker_ProcessWrapFnError(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path"}, + }, + }, + } + + mockRepo := repository.NewMockRepository(t) + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(errors.New("stage failed")) + mockProgress.On("SetTotal", mock.Anything, 1).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + + worker := NewWorker(nil, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.EqualError(t, err, "delete files from repository: stage failed") +} + +func TestDeleteWorker_ProcessDeleteFilesSuccess(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path1", "test/path2"}, + Ref: "main", + }, + }, + } + + mockRepo := &mockReaderWriter{ + MockRepository: repository.NewMockRepository(t), + } + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool { + return !opts.PushOnWrites && opts.Timeout == 10*time.Minute + }), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockRepo, false) + }) + + mockProgress.On("SetTotal", mock.Anything, 2).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + mockProgress.On("SetMessage", mock.Anything, "Deleting test/path1").Return() + mockProgress.On("SetMessage", mock.Anything, "Deleting test/path2").Return() + mockProgress.On("TooManyErrors").Return(nil).Twice() + + mockRepo.On("Delete", mock.Anything, "test/path1", "main", "Delete test/path1").Return(nil) + mockRepo.On("Delete", mock.Anything, "test/path2", "main", "Delete test/path2").Return(nil) + + mockProgress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Path == "test/path1" && result.Action == repository.FileActionDeleted && result.Error == nil + })).Return() + mockProgress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Path == "test/path2" && result.Action == repository.FileActionDeleted && result.Error == nil + })).Return() + + worker := NewWorker(nil, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) +} + +func TestDeleteWorker_ProcessDeleteFilesWithError(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path1", "test/path2"}, + Ref: "main", + }, + }, + } + + mockRepo := &mockReaderWriter{ + MockRepository: repository.NewMockRepository(t), + } + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockRepo, false) + }) + + mockProgress.On("SetTotal", mock.Anything, 2).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + mockProgress.On("SetMessage", mock.Anything, "Deleting test/path1").Return() + + deleteError := errors.New("delete failed") + mockRepo.On("Delete", mock.Anything, "test/path1", "main", "Delete test/path1").Return(deleteError) + + mockProgress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Path == "test/path1" && result.Action == repository.FileActionDeleted && errors.Is(result.Error, deleteError) + })).Return() + mockProgress.On("TooManyErrors").Return(errors.New("too many errors")) + + worker := NewWorker(nil, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.EqualError(t, err, "delete files from repository: too many errors") +} + +func TestDeleteWorker_ProcessWithSyncWorker(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path"}, + }, + }, + } + + mockRepo := &mockReaderWriter{ + MockRepository: repository.NewMockRepository(t), + } + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockSyncWorker := jobs.NewMockWorker(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockRepo, false) + }) + + mockProgress.On("SetTotal", mock.Anything, 1).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + mockProgress.On("SetMessage", mock.Anything, "Deleting test/path").Return() + mockProgress.On("TooManyErrors").Return(nil) + + mockRepo.On("Delete", mock.Anything, "test/path", "", "Delete test/path").Return(nil) + + mockProgress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Path == "test/path" && result.Action == repository.FileActionDeleted && result.Error == nil + })).Return() + + mockProgress.On("ResetResults").Return() + mockProgress.On("SetMessage", mock.Anything, "pull resources").Return() + + mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.MatchedBy(func(syncJob provisioning.Job) bool { + return syncJob.Spec.Pull != nil && !syncJob.Spec.Pull.Incremental + }), mockProgress).Return(nil) + + worker := NewWorker(mockSyncWorker, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) +} + +func TestDeleteWorker_ProcessSyncWorkerError(t *testing.T) { + job := provisioning.Job{ + Spec: provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"test/path"}, + }, + }, + } + + mockRepo := &mockReaderWriter{ + MockRepository: repository.NewMockRepository(t), + } + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockSyncWorker := jobs.NewMockWorker(t) + mockWrapFn := repository.NewMockWrapWithStageFn(t) + + mockWrapFn.On("Execute", mock.Anything, mockRepo, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOptions repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(mockRepo, false) + }) + + mockProgress.On("SetTotal", mock.Anything, 1).Return() + mockProgress.On("StrictMaxErrors", 1).Return() + mockProgress.On("SetMessage", mock.Anything, "Deleting test/path").Return() + mockProgress.On("TooManyErrors").Return(nil) + + mockRepo.On("Delete", mock.Anything, "test/path", "", "Delete test/path").Return(nil) + + mockProgress.On("Record", mock.Anything, mock.Anything).Return() + mockProgress.On("ResetResults").Return() + mockProgress.On("SetMessage", mock.Anything, "pull resources").Return() + + syncError := errors.New("sync failed") + mockSyncWorker.On("Process", mock.Anything, mockRepo, mock.Anything, mockProgress).Return(syncError) + + worker := NewWorker(mockSyncWorker, mockWrapFn.Execute) + err := worker.Process(context.Background(), mockRepo, job, mockProgress) + require.EqualError(t, err, "pull resources: sync failed") +} + +func TestDeleteWorker_deleteFiles(t *testing.T) { + tests := []struct { + name string + paths []string + deleteResults []error + tooManyErrors error + expectedError string + expectedCalls int + }{ + { + name: "single file success", + paths: []string{"test/file1.yaml"}, + deleteResults: []error{nil}, + expectedCalls: 1, + }, + { + name: "multiple files success", + paths: []string{"test/file1.yaml", "test/file2.yaml", "test/file3.yaml"}, + deleteResults: []error{nil, nil, nil}, + expectedCalls: 3, + }, + { + name: "single file with error continues", + paths: []string{"test/file1.yaml", "test/file2.yaml"}, + deleteResults: []error{errors.New("delete failed"), nil}, + expectedCalls: 2, + }, + { + name: "too many errors stops processing", + paths: []string{"test/file1.yaml", "test/file2.yaml", "test/file3.yaml"}, + deleteResults: []error{errors.New("delete failed")}, + tooManyErrors: errors.New("too many errors"), + expectedError: "too many errors", + expectedCalls: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRepo := &mockReaderWriter{ + MockRepository: repository.NewMockRepository(t), + } + mockProgress := jobs.NewMockJobProgressRecorder(t) + + opts := provisioning.DeleteJobOptions{ + Ref: "main", + } + + for i, path := range tt.paths { + if i < len(tt.deleteResults) { + mockRepo.On("Delete", mock.Anything, path, "main", "Delete "+path).Return(tt.deleteResults[i]).Once() + mockProgress.On("SetMessage", mock.Anything, "Deleting "+path).Return().Once() + mockProgress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Path == path && result.Action == repository.FileActionDeleted + })).Return().Once() + + if tt.tooManyErrors != nil && i == 0 { + mockProgress.On("TooManyErrors").Return(tt.tooManyErrors).Once() + } else { + mockProgress.On("TooManyErrors").Return(nil).Once() + } + } + } + + worker := NewWorker(nil, nil) + err := worker.deleteFiles(context.Background(), mockRepo, mockProgress, opts, tt.paths...) + + if tt.expectedError != "" { + require.EqualError(t, err, tt.expectedError) + } else { + require.NoError(t, err) + } + + mockRepo.AssertExpectations(t) + mockProgress.AssertExpectations(t) + }) + } +} diff --git a/pkg/registry/apis/provisioning/jobs/driver.go b/pkg/registry/apis/provisioning/jobs/driver.go index a3adaeb5550..8d31d1dde1a 100644 --- a/pkg/registry/apis/provisioning/jobs/driver.go +++ b/pkg/registry/apis/provisioning/jobs/driver.go @@ -175,10 +175,12 @@ func (d *jobDriver) claimAndProcessOneJob(ctx context.Context) error { jobctx, cancel := context.WithTimeout(ctx, d.jobTimeout) defer cancel() // Ensure resources are released when the function returns + recorder := newJobProgressRecorder(d.onProgress(job)) + // Process the job. start := time.Now() job.Status.Started = start.UnixMilli() - err = d.processJob(jobctx, job) // NOTE: We pass in a pointer here such that the job status can be kept in Complete without re-fetching. + err = d.processJob(jobctx, job, recorder) // NOTE: We pass in a pointer here such that the job status can be kept in Complete without re-fetching. end := time.Now() logger.Debug("job processed", "duration", end.Sub(start), "error", err) @@ -187,17 +189,7 @@ func (d *jobDriver) claimAndProcessOneJob(ctx context.Context) error { err = jobctx.Err() } - // Mark the job as failed and remove from queue - if err != nil { - job.Status.State = provisioning.JobStateError - job.Status.Errors = append(job.Status.Errors, err.Error()) - } - - job.Status.Progress = 0 // clear progressbar - job.Status.Finished = end.UnixMilli() - if !job.Status.State.Finished() { - job.Status.State = provisioning.JobStateSuccess // no error - } + job.Status = recorder.Complete(ctx, err) // Save the finished job err = d.historicJobs.WriteJob(ctx, job.DeepCopy()) @@ -217,7 +209,7 @@ func (d *jobDriver) claimAndProcessOneJob(ctx context.Context) error { return nil } -func (d *jobDriver) processJob(ctx context.Context, job *provisioning.Job) error { +func (d *jobDriver) processJob(ctx context.Context, job *provisioning.Job, recorder JobProgressRecorder) error { for _, worker := range d.workers { if !worker.IsSupported(ctx, *job) { continue @@ -228,16 +220,7 @@ func (d *jobDriver) processJob(ctx context.Context, job *provisioning.Job) error return apifmt.Errorf("failed to get repository '%s': %w", job.Spec.Repository, err) } - recorder := newJobProgressRecorder(d.onProgress(job)) - - err = worker.Process(ctx, repo, *job, recorder) - if err != nil { - return apifmt.Errorf("worker failed to process job: %w", err) - } - - job.Status = recorder.Complete(ctx, err) - - return nil + return worker.Process(ctx, repo, *job, recorder) } return apifmt.Errorf("no workers were registered to handle the job") diff --git a/pkg/registry/apis/provisioning/jobs/export/resources.go b/pkg/registry/apis/provisioning/jobs/export/resources.go index e02b7d7bfad..c226cacac00 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources.go @@ -38,21 +38,30 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, // When requesting v2 (or v0) dashboards over the v1 api, we want to keep the original apiVersion if conversion fails var shim conversionShim if kind.GroupResource() == resources.DashboardResource.GroupResource() { - var v2client dynamic.ResourceInterface + var v2clientAlphaV1, v2clientAlphaV2 dynamic.ResourceInterface shim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) { failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed") if failed { storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion") - // For v2 we need to request the original version - if strings.HasPrefix(storedVersion, "v2") { - if v2client == nil { - v2client, _, err = clients.ForResource(resources.DashboardResourceV2) + if strings.HasPrefix(storedVersion, "v2alpha1") { + if v2clientAlphaV1 == nil { + v2clientAlphaV1, _, err = clients.ForResource(resources.DashboardResourceV2alpha1) if err != nil { return nil, err } } - return v2client.Get(ctx, item.GetName(), metav1.GetOptions{}) + return v2clientAlphaV1.Get(ctx, item.GetName(), metav1.GetOptions{}) + } + + if strings.HasPrefix(storedVersion, "v2alpha2") { + if v2clientAlphaV2 == nil { + v2clientAlphaV2, _, err = clients.ForResource(resources.DashboardResourceV2alpha2) + if err != nil { + return nil, err + } + } + return v2clientAlphaV2.Get(ctx, item.GetName(), metav1.GetOptions{}) } // For v0 we can simply fallback -- the full model is saved, but @@ -97,6 +106,7 @@ func exportResource(ctx context.Context, if shim != nil { item, err = shim(ctx, item) } + if err == nil { result.Path, err = repositoryResources.WriteResourceFileFromObject(ctx, item, resources.WriteOptions{ Path: options.Path, diff --git a/pkg/registry/apis/provisioning/jobs/export/resources_test.go b/pkg/registry/apis/provisioning/jobs/export/resources_test.go index 64f7d256866..2e663b64e57 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources_test.go @@ -16,432 +16,519 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) -func TestExportResources_Dashboards(t *testing.T) { - tests := []struct { - name string - mockItems []unstructured.Unstructured - expectedError string - setupProgress func(progress *jobs.MockJobProgressRecorder) - setupResources func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) - }{ - { - name: "successful dashboard export", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-1", - }, - }, - }, - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-2", - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-1" && result.Action == repository.FileActionCreated - })).Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-2" && result.Action == repository.FileActionCreated - })).Return() - progress.On("TooManyErrors").Return(nil) - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "dashboard-1" - }), options).Return("dashboard-1.json", nil) - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "dashboard-2" - }), options).Return("dashboard-2.json", nil) +// Helper function to create dashboard objects +func createDashboardObject(name string) unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": name, }, }, - { - name: "client error", - mockItems: nil, - expectedError: "get client for dashboards: didn't work", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work")) - }, - }, - { - name: "dashboard export with errors", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-1", - }, - }, - }, - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-2", - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-1" && result.Action == repository.FileActionIgnored && result.Error != nil && result.Error.Error() == "failed to export dashboard" - })).Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-2" && result.Action == repository.FileActionCreated - })).Return() - progress.On("TooManyErrors").Return(nil) - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "dashboard-1" - }), options).Return("", fmt.Errorf("failed to export dashboard")) - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "dashboard-2" - }), options).Return("dashboard-2.json", nil) - }, - }, - { - name: "dashboard export too many errors", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-1", - }, - }, - }, - }, - expectedError: "export dashboards: too many errors encountered", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-1" && result.Action == repository.FileActionIgnored && result.Error != nil && result.Error.Error() == "failed to export dashboard" - })).Return() - progress.On("TooManyErrors").Return(fmt.Errorf("too many errors encountered")) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "dashboard-1" - }), options).Return("", fmt.Errorf("failed to export dashboard")) - }, - }, - { - name: "ignores existing dashboards", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "existing-dashboard", - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored - })).Return() - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - return obj.GetName() == "existing-dashboard" - }), options).Return("", resources.ErrAlreadyInRepository) - }, - }, - { - name: "uses saved dashboard version", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "existing-dashboard", - }, - "spec": map[string]interface{}{ - "hello": "world", - }, - "status": map[string]interface{}{ - "conversion": map[string]interface{}{ - "failed": true, - "storedVersion": "v0xyz", - }, - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored - })).Return() - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - - repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { - // Verify that the object has the expected status.conversion.storedVersion field - status, exists, err := unstructured.NestedMap(obj.Object, "status") - if !exists || err != nil { - return false - } - - conversion, exists, err := unstructured.NestedMap(status, "conversion") - if !exists || err != nil { - return false - } - - storedVersion, exists, err := unstructured.NestedString(conversion, "storedVersion") - if !exists || err != nil { - return false - } - - if storedVersion != "v0xyz" { - return false - } - - return obj.GetName() == "existing-dashboard" - }), options).Return("", fmt.Errorf("XXX")) - }, - }, - { - name: "dashboard with failed conversion but no stored version", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "dashboard-no-stored-version", - }, - "status": map[string]interface{}{ - "conversion": map[string]interface{}{ - "failed": true, - // No storedVersion field - }, - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "dashboard-no-stored-version" && - result.Action == repository.FileActionIgnored && - result.Error != nil - })).Return() - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - // The value is not saved - }, - }, - { - name: "handles v2 dashboard version", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "v2-dashboard", - }, - "status": map[string]interface{}{ - "conversion": map[string]interface{}{ - "failed": true, - "storedVersion": "v2", - }, - }, - }, - }, - }, - expectedError: "", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Name == "v2-dashboard" && result.Action == repository.FileActionCreated - })).Return() - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - // Setup v1 client - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - - // Setup v2 client - - // Mock v2 client Get call - v2Dashboard := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "dashboard.grafana.app/v2alpha1", - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "v2-dashboard", - }, - "spec": map[string]interface{}{ - "version": 2, - "title": "V2 Dashboard", - }, - }, - } - v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{*v2Dashboard}} - resourceClients.On("ForResource", resources.DashboardResourceV2).Return(v2Client, gvk, nil) - - options := resources.WriteOptions{ - Path: "grafana", - Ref: "feature/branch", - } - repoResources.On("WriteResourceFileFromObject", mock.Anything, v2Dashboard, options).Return("v2-dashboard.json", nil) - }, - }, - { - name: "handles v2 client creation error", - mockItems: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "apiVersion": resources.DashboardResource.GroupVersion().String(), - "kind": "Dashboard", - "metadata": map[string]interface{}{ - "name": "v2-dashboard-error", - }, - "status": map[string]interface{}{ - "conversion": map[string]interface{}{ - "failed": true, - "storedVersion": "v2", - }, - }, - }, - }, - }, - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - if result.Name != "v2-dashboard-error" { - return false - } - if result.Action != repository.FileActionIgnored { - return false - } - if result.Error == nil { - return false - } - - if result.Error.Error() != "v2 client error" { - return false - } - - return true - })).Return() - progress.On("TooManyErrors").Return(nil) - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) - resourceClients.On("ForResource", resources.DashboardResourceV2).Return(nil, gvk, fmt.Errorf("v2 client error")) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockClient := &mockDynamicInterface{ - items: tt.mockItems, - } - - resourceClients := resources.NewMockResourceClients(t) - mockProgress := jobs.NewMockJobProgressRecorder(t) - tt.setupProgress(mockProgress) - - repoResources := resources.NewMockRepositoryResources(t) - tt.setupResources(repoResources, resourceClients, mockClient, schema.GroupVersionKind{ - Group: resources.DashboardResource.Group, - Version: resources.DashboardResource.Version, - Kind: "DashboardList", - }) - - options := provisioningV0.ExportJobOptions{ - Path: "grafana", - Branch: "feature/branch", - } - - err := ExportResources(context.Background(), options, resourceClients, repoResources, mockProgress) - if tt.expectedError != "" { - require.EqualError(t, err, tt.expectedError) - } else { - require.NoError(t, err) - } - - mockProgress.AssertExpectations(t) - repoResources.AssertExpectations(t) - resourceClients.AssertExpectations(t) - }) } } + +// Helper function to create v2 dashboard objects +func createV2DashboardObject(name, version string) unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": fmt.Sprintf("dashboard.grafana.app/%s", version), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": name, + }, + "spec": map[string]interface{}{ + "version": 2, + "title": "V2 Dashboard", + }, + }, + } +} + +// Helper function to run export test +func runExportTest(t *testing.T, mockItems []unstructured.Unstructured, setupProgress func(*jobs.MockJobProgressRecorder), setupResources func(*resources.MockRepositoryResources, *resources.MockResourceClients, *mockDynamicInterface, schema.GroupVersionKind)) error { + mockClient := &mockDynamicInterface{ + items: mockItems, + } + + resourceClients := resources.NewMockResourceClients(t) + mockProgress := jobs.NewMockJobProgressRecorder(t) + setupProgress(mockProgress) + + repoResources := resources.NewMockRepositoryResources(t) + setupResources(repoResources, resourceClients, mockClient, schema.GroupVersionKind{ + Group: resources.DashboardResource.Group, + Version: resources.DashboardResource.Version, + Kind: "DashboardList", + }) + + options := provisioningV0.ExportJobOptions{ + Path: "grafana", + Branch: "feature/branch", + } + + err := ExportResources(context.Background(), options, resourceClients, repoResources, mockProgress) + + mockProgress.AssertExpectations(t) + repoResources.AssertExpectations(t) + resourceClients.AssertExpectations(t) + + return err +} + +func TestExportResources_Dashboards_Success(t *testing.T) { + mockItems := []unstructured.Unstructured{ + createDashboardObject("dashboard-1"), + createDashboardObject("dashboard-2"), + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-1" && result.Action == repository.FileActionCreated + })).Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-2" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "dashboard-1" + }), options).Return("dashboard-1.json", nil) + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "dashboard-2" + }), options).Return("dashboard-2.json", nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_ClientError(t *testing.T) { + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work")) + } + + err := runExportTest(t, nil, setupProgress, setupResources) + require.EqualError(t, err, "get client for dashboards: didn't work") +} + +func TestExportResources_Dashboards_WithErrors(t *testing.T) { + mockItems := []unstructured.Unstructured{ + createDashboardObject("dashboard-1"), + createDashboardObject("dashboard-2"), + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-1" && result.Action == repository.FileActionIgnored && result.Error != nil && result.Error.Error() == "failed to export dashboard" + })).Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-2" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "dashboard-1" + }), options).Return("", fmt.Errorf("failed to export dashboard")) + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "dashboard-2" + }), options).Return("dashboard-2.json", nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_TooManyErrors(t *testing.T) { + mockItems := []unstructured.Unstructured{ + createDashboardObject("dashboard-1"), + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-1" && result.Action == repository.FileActionIgnored && result.Error != nil && result.Error.Error() == "failed to export dashboard" + })).Return() + progress.On("TooManyErrors").Return(fmt.Errorf("too many errors encountered")) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "dashboard-1" + }), options).Return("", fmt.Errorf("failed to export dashboard")) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.EqualError(t, err, "export dashboards: too many errors encountered") +} + +func TestExportResources_Dashboards_IgnoresExisting(t *testing.T) { + mockItems := []unstructured.Unstructured{ + createDashboardObject("existing-dashboard"), + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + return obj.GetName() == "existing-dashboard" + }), options).Return("", resources.ErrAlreadyInRepository) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_SavedVersion(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "existing-dashboard", + }, + "spec": map[string]interface{}{ + "hello": "world", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v0xyz", + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + // Verify that the object has the expected status.conversion.storedVersion field + status, exists, err := unstructured.NestedMap(obj.Object, "status") + if !exists || err != nil { + return false + } + + conversion, exists, err := unstructured.NestedMap(status, "conversion") + if !exists || err != nil { + return false + } + + storedVersion, exists, err := unstructured.NestedString(conversion, "storedVersion") + if !exists || err != nil { + return false + } + + if storedVersion != "v0xyz" { + return false + } + + return obj.GetName() == "existing-dashboard" + }), options).Return("", fmt.Errorf("XXX")) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_FailedConversionNoStoredVersion(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-no-stored-version", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + // No storedVersion field + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-no-stored-version" && + result.Action == repository.FileActionIgnored && + result.Error != nil + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + // The value is not saved + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_V2Alpha1(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2alpha1", + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "v2-dashboard" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + // Setup v1 client + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + + // Setup v2 client + v2Dashboard := createV2DashboardObject("v2-dashboard", "v2alpha1") + v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{v2Dashboard}} + resourceClients.On("ForResource", resources.DashboardResourceV2alpha1).Return(v2Client, gvk, nil) + + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + repoResources.On("WriteResourceFileFromObject", mock.Anything, &v2Dashboard, options).Return("v2-dashboard.json", nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_V2Alpha1_ClientError(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard-error", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2alpha1", + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + if result.Name != "v2-dashboard-error" { + return false + } + if result.Action != repository.FileActionIgnored { + return false + } + if result.Error == nil { + return false + } + + if result.Error.Error() != "v2 client error" { + return false + } + + return true + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResourceV2alpha1).Return(nil, gvk, fmt.Errorf("v2 client error")) + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_V2Alpha2(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2alpha2", + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "v2-dashboard" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + // Setup v1 client + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + + // Setup v2 client + v2Dashboard := createV2DashboardObject("v2-dashboard", "v2alpha2") + v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{v2Dashboard}} + resourceClients.On("ForResource", resources.DashboardResourceV2alpha2).Return(v2Client, gvk, nil) + + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + repoResources.On("WriteResourceFileFromObject", mock.Anything, &v2Dashboard, options).Return("v2-dashboard.json", nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} + +func TestExportResources_Dashboards_V2Alpha2_ClientError(t *testing.T) { + mockItems := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard-error", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2alpha2", + }, + }, + }, + }, + } + + setupProgress := func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + if result.Name != "v2-dashboard-error" { + return false + } + if result.Action != repository.FileActionIgnored { + return false + } + if result.Error == nil { + return false + } + + if result.Error.Error() != "v2 client error" { + return false + } + + return true + })).Return() + progress.On("TooManyErrors").Return(nil) + } + + setupResources := func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResourceV2alpha2).Return(nil, gvk, fmt.Errorf("v2 client error")) + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + } + + err := runExportTest(t, mockItems, setupProgress, setupResources) + require.NoError(t, err) +} diff --git a/pkg/registry/apis/provisioning/jobs/export/worker.go b/pkg/registry/apis/provisioning/jobs/export/worker.go index 5ded8555257..13536a62aa5 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker.go @@ -56,9 +56,17 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository, return err } + msg := options.Message + if msg == "" { + msg = fmt.Sprintf("Export from Grafana %s", job.Name) + } + cloneOptions := repository.StageOptions{ - Timeout: 10 * time.Minute, - PushOnWrites: false, + Ref: options.Branch, + Timeout: 10 * time.Minute, + PushOnWrites: false, + Mode: repository.StageModeCommitOnlyOnce, + CommitOnlyOnceMessage: msg, } fn := func(repo repository.Repository, _ bool) error { diff --git a/pkg/registry/apis/provisioning/jobs/export/worker_test.go b/pkg/registry/apis/provisioning/jobs/export/worker_test.go index aa5f2ea1c5e..930b5f051a9 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker_test.go @@ -228,7 +228,9 @@ func TestExportWorker_ProcessStageOptions(t *testing.T) { job := v0alpha1.Job{ Spec: v0alpha1.JobSpec{ Action: v0alpha1.JobActionPush, - Push: &v0alpha1.ExportJobOptions{}, + Push: &v0alpha1.ExportJobOptions{ + Branch: "feature-branch", + }, }, } @@ -239,7 +241,8 @@ func TestExportWorker_ProcessStageOptions(t *testing.T) { Namespace: "test-namespace", }, Spec: v0alpha1.RepositorySpec{ - Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, + Type: v0alpha1.GitRepositoryType, + Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow, v0alpha1.BranchWorkflow}, }, }) @@ -258,9 +261,11 @@ func TestExportWorker_ProcessStageOptions(t *testing.T) { mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mockStageFn := NewMockWrapWithStageFn(t) - // Verify clone and push options + // Verify all stage options including Ref (branch), Timeout, and PushOnWrites mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool { - return opts.Timeout == 10*time.Minute && !opts.PushOnWrites + return opts.Ref == "feature-branch" && + opts.Timeout == 10*time.Minute && + !opts.PushOnWrites && opts.Mode == repository.StageModeCommitOnlyOnce }), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { return fn(repo, true) }) @@ -270,6 +275,89 @@ func TestExportWorker_ProcessStageOptions(t *testing.T) { require.NoError(t, err) } +func TestExportWorker_ProcessStageOptionsWithBranch(t *testing.T) { + tests := []struct { + name string + branch string + expectedRef string + workflows []v0alpha1.Workflow + repoType v0alpha1.RepositoryType + }{ + { + name: "branch specified", + branch: "develop", + expectedRef: "develop", + workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow, v0alpha1.BranchWorkflow}, + repoType: v0alpha1.GitRepositoryType, + }, + { + name: "empty branch", + branch: "", + expectedRef: "", + workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, + repoType: v0alpha1.LocalRepositoryType, + }, + { + name: "main branch", + branch: "main", + expectedRef: "main", + workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow, v0alpha1.BranchWorkflow}, + repoType: v0alpha1.GitRepositoryType, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + job := v0alpha1.Job{ + Spec: v0alpha1.JobSpec{ + Action: v0alpha1.JobActionPush, + Push: &v0alpha1.ExportJobOptions{ + Branch: tt.branch, + }, + }, + } + + mockRepo := repository.NewMockRepository(t) + mockRepo.On("Config").Return(&v0alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + Type: tt.repoType, + Workflows: tt.workflows, + }, + }) + + mockProgress := jobs.NewMockJobProgressRecorder(t) + mockClients := resources.NewMockClientFactory(t) + mockResourceClients := resources.NewMockResourceClients(t) + mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) + + mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesClient := resources.NewMockRepositoryResources(t) + mockRepoResources.On("Client", mock.Anything, mock.Anything).Return(mockRepoResourcesClient, nil) + + mockExportFn := NewMockExportFn(t) + mockExportFn.On("Execute", mock.Anything, "test-repo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + mockStageFn := NewMockWrapWithStageFn(t) + // Verify that the stage options contain the correct branch reference and other parameters + mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool { + return opts.Ref == tt.expectedRef && + opts.Timeout == 10*time.Minute && + !opts.PushOnWrites + }), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { + return fn(repo, true) + }) + + r := NewExportWorker(mockClients, mockRepoResources, mockExportFn.Execute, mockStageFn.Execute) + err := r.Process(context.Background(), mockRepo, job, mockProgress) + require.NoError(t, err) + }) + } +} + func TestExportWorker_ProcessExportFnError(t *testing.T) { job := v0alpha1.Job{ Spec: v0alpha1.JobSpec{ @@ -406,7 +494,7 @@ func TestExportWorker_ProcessGitRepository(t *testing.T) { mockStageFn := NewMockWrapWithStageFn(t) // Verify clone and push options mockStageFn.On("Execute", mock.Anything, mockRepo, mock.MatchedBy(func(opts repository.StageOptions) bool { - return opts.Timeout == 10*time.Minute && !opts.PushOnWrites + return opts.Timeout == 10*time.Minute && opts.Mode == repository.StageModeCommitOnlyOnce }), mock.Anything).Return(func(ctx context.Context, repo repository.Repository, stageOpts repository.StageOptions, fn func(repository.Repository, bool) error) error { return fn(repo, true) }) diff --git a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go index 7ead8a206e7..54b07e6a38a 100644 --- a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go +++ b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.4. DO NOT EDIT. +// Code generated by mockery v2.52.4. DO NOT EDIT. package jobs @@ -237,34 +237,35 @@ func (_c *MockJobProgressRecorder_SetTotal_Call) RunAndReturn(run func(context.C return _c } -// Strict provides a mock function with no fields -func (_m *MockJobProgressRecorder) Strict() { - _m.Called() +// StrictMaxErrors provides a mock function with given fields: maxErrors +func (_m *MockJobProgressRecorder) StrictMaxErrors(maxErrors int) { + _m.Called(maxErrors) } -// MockJobProgressRecorder_Strict_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Strict' -type MockJobProgressRecorder_Strict_Call struct { +// MockJobProgressRecorder_StrictMaxErrors_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StrictMaxErrors' +type MockJobProgressRecorder_StrictMaxErrors_Call struct { *mock.Call } -// Strict is a helper method to define mock.On call -func (_e *MockJobProgressRecorder_Expecter) Strict() *MockJobProgressRecorder_Strict_Call { - return &MockJobProgressRecorder_Strict_Call{Call: _e.mock.On("Strict")} +// StrictMaxErrors is a helper method to define mock.On call +// - maxErrors int +func (_e *MockJobProgressRecorder_Expecter) StrictMaxErrors(maxErrors interface{}) *MockJobProgressRecorder_StrictMaxErrors_Call { + return &MockJobProgressRecorder_StrictMaxErrors_Call{Call: _e.mock.On("StrictMaxErrors", maxErrors)} } -func (_c *MockJobProgressRecorder_Strict_Call) Run(run func()) *MockJobProgressRecorder_Strict_Call { +func (_c *MockJobProgressRecorder_StrictMaxErrors_Call) Run(run func(maxErrors int)) *MockJobProgressRecorder_StrictMaxErrors_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(int)) }) return _c } -func (_c *MockJobProgressRecorder_Strict_Call) Return() *MockJobProgressRecorder_Strict_Call { +func (_c *MockJobProgressRecorder_StrictMaxErrors_Call) Return() *MockJobProgressRecorder_StrictMaxErrors_Call { _c.Call.Return() return _c } -func (_c *MockJobProgressRecorder_Strict_Call) RunAndReturn(run func()) *MockJobProgressRecorder_Strict_Call { +func (_c *MockJobProgressRecorder_StrictMaxErrors_Call) RunAndReturn(run func(int)) *MockJobProgressRecorder_StrictMaxErrors_Call { _c.Run(run) return _c } diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy.go index 191e0c4922f..8b253bebd7f 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy.go @@ -35,12 +35,26 @@ func NewLegacyMigrator( func (m *LegacyMigrator) Migrate(ctx context.Context, rw repository.ReaderWriter, options provisioning.MigrateJobOptions, progress jobs.JobProgressRecorder) error { namespace := rw.Config().Namespace + var stageMode repository.StageMode + if options.History { + // When History is true, we want to commit and push each file (previous PushOnWrites: true) + stageMode = repository.StageModeCommitAndPushOnEach + } else { + // When History is false, we want to commit only once (previous CommitOnlyOnce: true) + stageMode = repository.StageModeCommitOnlyOnce + } + stageOptions := repository.StageOptions{ - PushOnWrites: options.History, + Mode: stageMode, + CommitOnlyOnceMessage: options.Message, // TODO: make this configurable Timeout: 10 * time.Minute, } + // Fail if migrating at least one + progress.StrictMaxErrors(1) + progress.SetMessage(ctx, "migrating legacy resources") + if err := m.wrapWithStageFn(ctx, rw, stageOptions, func(repo repository.Repository, staged bool) error { rw, ok := repo.(repository.ReaderWriter) if !ok { diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go index 877bd01fd33..3b25de7aeaf 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go @@ -34,6 +34,8 @@ func TestWrapWithStageFn(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() // Execute repo := repository.NewMockRepository(t) @@ -67,6 +69,8 @@ func TestWrapWithCloneFn_Error(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() // Execute repo := repository.NewMockRepository(t) repo.On("Config").Return(&provisioning.Repository{ @@ -109,6 +113,8 @@ func TestLegacyMigrator_MigrateFails(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() // Execute repo := repository.NewMockRepository(t) @@ -158,7 +164,9 @@ func TestLegacyMigrator_ResetUnifiedStorageFails(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, mock.Anything).Return() + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() + progress.On("SetMessage", mock.Anything, "resetting unified storage").Return() // Execute repo := repository.NewMockRepository(t) @@ -213,8 +221,12 @@ func TestLegacyMigrator_SyncFails(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, mock.Anything).Return() + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() + progress.On("SetMessage", mock.Anything, "resetting unified storage").Return() progress.On("ResetResults").Return() + progress.On("SetMessage", mock.Anything, "pulling resources").Return() + progress.On("SetMessage", mock.Anything, "error importing resources, reverting").Return() // Execute repo := repository.NewMockRepository(t) @@ -268,8 +280,12 @@ func TestLegacyMigrator_SyncFails(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, mock.Anything).Return() + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() + progress.On("SetMessage", mock.Anything, "resetting unified storage").Return() progress.On("ResetResults").Return() + progress.On("SetMessage", mock.Anything, "pulling resources").Return() + progress.On("SetMessage", mock.Anything, "error importing resources, reverting").Return() // Execute repo := repository.NewMockRepository(t) @@ -321,8 +337,11 @@ func TestLegacyMigrator_Success(t *testing.T) { ) progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, mock.Anything).Return() + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() + progress.On("SetMessage", mock.Anything, "resetting unified storage").Return() progress.On("ResetResults").Return() + progress.On("SetMessage", mock.Anything, "pulling resources").Return() // Execute repo := repository.NewMockRepository(t) @@ -364,6 +383,8 @@ func TestLegacyMigrator_BeforeFnExecution(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) // No progress messages expected in current staging implementation + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() // Execute repo := repository.NewMockRepository(t) @@ -398,6 +419,8 @@ func TestLegacyMigrator_ProgressScanner(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) // No progress messages expected in current staging implementation + progress.On("StrictMaxErrors", 1).Return() + progress.On("SetMessage", mock.Anything, "migrating legacy resources").Return() repo := repository.NewMockRepository(t) repo.On("Config").Return(&provisioning.Repository{ diff --git a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go index e2caeb7b3f4..4f9f33b96ec 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go @@ -33,9 +33,13 @@ func NewUnifiedStorageMigrator( func (m *UnifiedStorageMigrator) Migrate(ctx context.Context, repo repository.ReaderWriter, options provisioning.MigrateJobOptions, progress jobs.JobProgressRecorder) error { namespace := repo.Config().GetNamespace() progress.SetMessage(ctx, "export resources") + progress.StrictMaxErrors(1) // strict as we want the entire instance to be managed + exportJob := provisioning.Job{ Spec: provisioning.JobSpec{ - Push: &provisioning.ExportJobOptions{}, + Push: &provisioning.ExportJobOptions{ + Message: options.Message, + }, }, } if err := m.exportWorker.Process(ctx, repo, exportJob, progress); err != nil { diff --git a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go index 992f99cb299..3cb48119842 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go @@ -30,6 +30,7 @@ func TestUnifiedStorageMigrator_Migrate(t *testing.T) { }, }) pr.On("SetMessage", mock.Anything, "export resources").Return() + pr.On("StrictMaxErrors", 1).Return() ew.On("Process", mock.Anything, rw, mock.MatchedBy(func(job provisioning.Job) bool { return job.Spec.Push != nil }), pr).Return(errors.New("export failed")) @@ -46,6 +47,7 @@ func TestUnifiedStorageMigrator_Migrate(t *testing.T) { }, }) pr.On("SetMessage", mock.Anything, "export resources").Return() + pr.On("StrictMaxErrors", 1).Return() ew.On("Process", mock.Anything, rw, mock.MatchedBy(func(job provisioning.Job) bool { return job.Spec.Push != nil }), pr).Return(nil) @@ -67,6 +69,7 @@ func TestUnifiedStorageMigrator_Migrate(t *testing.T) { }, }) pr.On("SetMessage", mock.Anything, "export resources").Return() + pr.On("StrictMaxErrors", 1).Return() nc.On("Clean", mock.Anything, "test-namespace", pr).Return(errors.New("clean failed")) // Export and sync jobs succeed @@ -92,6 +95,7 @@ func TestUnifiedStorageMigrator_Migrate(t *testing.T) { }, }) pr.On("SetMessage", mock.Anything, "export resources").Return() + pr.On("StrictMaxErrors", 1).Return() // Export job succeeds ew.On("Process", mock.Anything, rw, mock.MatchedBy(func(job provisioning.Job) bool { return job.Spec.Push != nil diff --git a/pkg/registry/apis/provisioning/jobs/migrate/worker.go b/pkg/registry/apis/provisioning/jobs/migrate/worker.go index 736ecfc81b4..625b49ea32c 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/worker.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/worker.go @@ -43,7 +43,6 @@ func (w *MigrationWorker) Process(ctx context.Context, repo repository.Repositor return errors.New("missing migrate settings") } - progress.Strict() progress.SetTotal(ctx, 10) // will show a progress bar rw, ok := repo.(repository.ReaderWriter) if !ok { diff --git a/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go b/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go index 31242a48189..e7d8c7c6175 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go @@ -62,7 +62,6 @@ func TestMigrationWorker_ProcessNotReaderWriter(t *testing.T) { } progressRecorder := jobs.NewMockJobProgressRecorder(t) progressRecorder.On("SetTotal", mock.Anything, 10).Return() - progressRecorder.On("Strict").Return() repo := repository.NewMockReader(t) err := worker.Process(context.Background(), repo, job, progressRecorder) @@ -87,7 +86,6 @@ func TestMigrationWorker_WithHistory(t *testing.T) { t.Run("fail local", func(t *testing.T) { progressRecorder := jobs.NewMockJobProgressRecorder(t) progressRecorder.On("SetTotal", mock.Anything, 10).Return() - progressRecorder.On("Strict").Return() repo := local.NewLocal(&provisioning.Repository{}, nil) err := worker.Process(context.Background(), repo, job, progressRecorder) @@ -97,7 +95,6 @@ func TestMigrationWorker_WithHistory(t *testing.T) { t.Run("fail unified", func(t *testing.T) { progressRecorder := jobs.NewMockJobProgressRecorder(t) progressRecorder.On("SetTotal", mock.Anything, 10).Return() - progressRecorder.On("Strict").Return() repo := repository.NewMockRepository(t) repo.On("Config").Return(&provisioning.Repository{ @@ -144,7 +141,6 @@ func TestMigrationWorker_Process(t *testing.T) { isLegacyActive: true, setupMocks: func(lm *MockMigrator, um *MockMigrator, ds *dualwrite.MockService, pr *jobs.MockJobProgressRecorder) { pr.On("SetTotal", mock.Anything, 10).Return() - pr.On("Strict").Return() ds.On("ReadFromUnified", mock.Anything, mock.Anything).Return(false, nil) lm.On("Migrate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) }, @@ -160,7 +156,6 @@ func TestMigrationWorker_Process(t *testing.T) { isLegacyActive: false, setupMocks: func(lm *MockMigrator, um *MockMigrator, ds *dualwrite.MockService, pr *jobs.MockJobProgressRecorder) { pr.On("SetTotal", mock.Anything, 10).Return() - pr.On("Strict").Return() ds.On("ReadFromUnified", mock.Anything, mock.Anything).Return(true, nil) um.On("Migrate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) }, @@ -176,7 +171,6 @@ func TestMigrationWorker_Process(t *testing.T) { isLegacyActive: true, setupMocks: func(lm *MockMigrator, um *MockMigrator, ds *dualwrite.MockService, pr *jobs.MockJobProgressRecorder) { pr.On("SetTotal", mock.Anything, 10).Return() - pr.On("Strict").Return() ds.On("ReadFromUnified", mock.Anything, mock.Anything).Return(false, nil) lm.On("Migrate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("migration failed")) }, diff --git a/pkg/registry/apis/provisioning/jobs/progress.go b/pkg/registry/apis/provisioning/jobs/progress.go index d23abcd9cd2..081ac9a3b86 100644 --- a/pkg/registry/apis/provisioning/jobs/progress.go +++ b/pkg/registry/apis/provisioning/jobs/progress.go @@ -60,8 +60,7 @@ type jobProgressRecorder struct { func newJobProgressRecorder(ProgressFn ProgressFn) JobProgressRecorder { return &jobProgressRecorder{ - maxErrors: 20, - started: time.Now(), + started: time.Now(), // Have a faster notifier for messages and total notifyImmediatelyFn: maybeNotifyProgress(500*time.Millisecond, ProgressFn), maybeNotifyFn: maybeNotifyProgress(5*time.Second, ProgressFn), @@ -126,9 +125,9 @@ func (r *jobProgressRecorder) SetTotal(ctx context.Context, total int) { r.notifyImmediately(ctx) } -func (r *jobProgressRecorder) Strict() { +func (r *jobProgressRecorder) StrictMaxErrors(maxErrors int) { r.mu.Lock() - r.maxErrors = 1 + r.maxErrors = maxErrors r.mu.Unlock() } @@ -136,7 +135,7 @@ func (r *jobProgressRecorder) TooManyErrors() error { r.mu.RLock() defer r.mu.RUnlock() - if r.errorCount >= r.maxErrors { + if r.maxErrors > 0 && r.errorCount >= r.maxErrors { return fmt.Errorf("too many errors: %d", r.errorCount) } @@ -256,8 +255,13 @@ func (r *jobProgressRecorder) Complete(ctx context.Context, err error) provision // Check for errors during execution if len(jobStatus.Errors) > 0 && jobStatus.State != provisioning.JobStateError { - jobStatus.State = provisioning.JobStateError - jobStatus.Message = "completed with errors" + if r.TooManyErrors() != nil { + jobStatus.Message = "completed with too many errors" + jobStatus.State = provisioning.JobStateError + } else { + jobStatus.Message = "completed with errors" + jobStatus.State = provisioning.JobStateWarning + } } // Override message if progress have a more explicit message diff --git a/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go b/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go index cfe7e46f88b..fd634d60844 100644 --- a/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go +++ b/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.4. DO NOT EDIT. +// Code generated by mockery v2.52.4. DO NOT EDIT. package jobs diff --git a/pkg/registry/apis/provisioning/jobs/queue.go b/pkg/registry/apis/provisioning/jobs/queue.go index 0502fb7886a..32b935e4be8 100644 --- a/pkg/registry/apis/provisioning/jobs/queue.go +++ b/pkg/registry/apis/provisioning/jobs/queue.go @@ -24,7 +24,7 @@ type JobProgressRecorder interface { SetMessage(ctx context.Context, msg string) SetTotal(ctx context.Context, total int) TooManyErrors() error - Strict() + StrictMaxErrors(maxErrors int) Complete(ctx context.Context, err error) provisioning.JobStatus } diff --git a/pkg/registry/apis/provisioning/jobs/repo_getter_mock.go b/pkg/registry/apis/provisioning/jobs/repo_getter_mock.go index b7dc38de650..d0152e725bc 100644 --- a/pkg/registry/apis/provisioning/jobs/repo_getter_mock.go +++ b/pkg/registry/apis/provisioning/jobs/repo_getter_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.4. DO NOT EDIT. +// Code generated by mockery v2.52.4. DO NOT EDIT. package jobs diff --git a/pkg/registry/apis/provisioning/jobs/sync/sync_test.go b/pkg/registry/apis/provisioning/jobs/sync/sync_test.go index 943e32da35c..f7bb4e0c961 100644 --- a/pkg/registry/apis/provisioning/jobs/sync/sync_test.go +++ b/pkg/registry/apis/provisioning/jobs/sync/sync_test.go @@ -33,6 +33,11 @@ func (m *mockReaderWriter) CompareFiles(ctx context.Context, base, ref string) ( return m.MockVersioned.CompareFiles(ctx, base, ref) } +func (m *mockReaderWriter) Move(ctx context.Context, oldPath, newPath, ref, message string) error { + args := m.MockRepository.Called(ctx, oldPath, newPath, ref, message) + return args.Error(0) +} + func TestSyncer_Sync(t *testing.T) { tests := []struct { name string diff --git a/pkg/registry/apis/provisioning/jobs/sync/worker.go b/pkg/registry/apis/provisioning/jobs/sync/worker.go index 0fc3d839834..5b8eb701b34 100644 --- a/pkg/registry/apis/provisioning/jobs/sync/worker.go +++ b/pkg/registry/apis/provisioning/jobs/sync/worker.go @@ -97,6 +97,8 @@ func (r *SyncWorker) Process(ctx context.Context, repo repository.Repository, jo } progress.SetMessage(ctx, "execute sync job") + progress.StrictMaxErrors(20) // make it stop after 20 errors + currentRef, syncError := r.syncer.Sync(ctx, rw, *job.Spec.Pull, repositoryResources, clients, progress) jobStatus := progress.Complete(ctx, syncError) syncStatus = jobStatus.ToSyncStatus(job.Name) diff --git a/pkg/registry/apis/provisioning/jobs/sync/worker_test.go b/pkg/registry/apis/provisioning/jobs/sync/worker_test.go index b0171fc7fc3..02e152afb6a 100644 --- a/pkg/registry/apis/provisioning/jobs/sync/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/sync/worker_test.go @@ -225,6 +225,7 @@ func TestSyncWorker_Process(t *testing.T) { // Sync execution succeeds pr.On("SetMessage", mock.Anything, "execute sync job").Return() + pr.On("StrictMaxErrors", 20).Return() s.On("Sync", mock.Anything, rw, mock.MatchedBy(func(opts provisioning.SyncJobOptions) bool { return true // Add specific sync options validation if needed }), mockRepoResources, mock.Anything, pr).Return("new-ref", nil) @@ -277,6 +278,7 @@ func TestSyncWorker_Process(t *testing.T) { // Sync execution fails pr.On("SetMessage", mock.Anything, "execute sync job").Return() + pr.On("StrictMaxErrors", 20).Return() syncError := errors.New("sync operation failed") s.On("Sync", mock.Anything, rw, mock.MatchedBy(func(opts provisioning.SyncJobOptions) bool { return true // Add specific sync options validation if needed @@ -317,6 +319,7 @@ func TestSyncWorker_Process(t *testing.T) { mockClients := resources.NewMockResourceClients(t) cf.On("Clients", mock.Anything, mock.Anything).Return(mockClients, nil) pr.On("SetMessage", mock.Anything, mock.Anything).Return() + pr.On("StrictMaxErrors", 20).Return() pr.On("Complete", mock.Anything, mock.Anything).Return(provisioning.JobStatus{State: provisioning.JobStateSuccess}) rpf.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return(nil) s.On("Sync", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("new-ref", nil) @@ -348,6 +351,7 @@ func TestSyncWorker_Process(t *testing.T) { mockClients := resources.NewMockResourceClients(t) cf.On("Clients", mock.Anything, mock.Anything).Return(mockClients, nil) pr.On("SetMessage", mock.Anything, mock.Anything).Return() + pr.On("StrictMaxErrors", 20).Return() pr.On("Complete", mock.Anything, mock.Anything).Return(provisioning.JobStatus{State: provisioning.JobStateSuccess}) s.On("Sync", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("new-ref", nil) }, @@ -407,6 +411,7 @@ func TestSyncWorker_Process(t *testing.T) { mockClients := resources.NewMockResourceClients(t) cf.On("Clients", mock.Anything, mock.Anything).Return(mockClients, nil) pr.On("SetMessage", mock.Anything, mock.Anything).Return() + pr.On("StrictMaxErrors", 20).Return() pr.On("Complete", mock.Anything, mock.Anything).Return(provisioning.JobStatus{State: provisioning.JobStateSuccess}) s.On("Sync", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("new-ref", nil) }, @@ -459,6 +464,7 @@ func TestSyncWorker_Process(t *testing.T) { mockClients := resources.NewMockResourceClients(t) cf.On("Clients", mock.Anything, mock.Anything).Return(mockClients, nil) pr.On("SetMessage", mock.Anything, mock.Anything).Return() + pr.On("StrictMaxErrors", 20).Return() pr.On("Complete", mock.Anything, mock.Anything).Return(provisioning.JobStatus{State: provisioning.JobStateSuccess}) s.On("Sync", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("new-ref", nil) }, @@ -489,6 +495,7 @@ func TestSyncWorker_Process(t *testing.T) { // Sync succeeds pr.On("SetMessage", mock.Anything, mock.Anything).Return() + pr.On("StrictMaxErrors", 20).Return() s.On("Sync", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("new-ref", nil) pr.On("Complete", mock.Anything, nil).Return(provisioning.JobStatus{State: provisioning.JobStateSuccess}) diff --git a/pkg/registry/apis/provisioning/jobs/worker_mock.go b/pkg/registry/apis/provisioning/jobs/worker_mock.go index 59e3c0df1b4..b264b258d1c 100644 --- a/pkg/registry/apis/provisioning/jobs/worker_mock.go +++ b/pkg/registry/apis/provisioning/jobs/worker_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.4. DO NOT EDIT. +// Code generated by mockery v2.52.4. DO NOT EDIT. package jobs diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 4e4ba04d578..be4cf583824 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -43,6 +43,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" "github.com/grafana/grafana/pkg/registry/apis/provisioning/controller" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" + deletepkg "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/delete" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/migrate" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/sync" @@ -54,6 +55,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature" "github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/usage" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -74,7 +76,8 @@ var ( ) type APIBuilder struct { - features featuremgmt.FeatureToggles + features featuremgmt.FeatureToggles + usageStats usagestats.Service tracer tracing.Tracer getter rest.Getter @@ -117,6 +120,7 @@ func NewAPIBuilder( ghFactory *github.Factory, legacyMigrator legacy.LegacyMigrator, storageStatus dualwrite.Service, + usageStats usagestats.Service, repositorySecrets secrets.RepositorySecrets, access authlib.AccessChecker, tracer tracing.Tracer, @@ -134,6 +138,7 @@ func NewAPIBuilder( b := &APIBuilder{ mutators: mutators, tracer: tracer, + usageStats: usageStats, localFileResolver: local, features: features, ghFactory: ghFactory, @@ -184,7 +189,7 @@ func RegisterAPIService( access authlib.AccessClient, legacyMigrator legacy.LegacyMigrator, storageStatus dualwrite.Service, - usageStatsService usagestats.Service, + usageStats usagestats.Service, repositorySecrets secrets.RepositorySecrets, tracer tracing.Tracer, extraBuilders []ExtraBuilder, @@ -202,13 +207,13 @@ func RegisterAPIService( filepath.Join(cfg.DataPath, "clone"), // where repositories are cloned (temporarialy for now) configProvider, ghFactory, legacyMigrator, storageStatus, + usageStats, repositorySecrets, access, tracer, extraBuilders, ) apiregistration.RegisterAPI(builder) - usageStatsService.RegisterMetricsFunc(builder.collectProvisioningStats) return builder, nil } @@ -434,41 +439,6 @@ func (b *APIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admis r.Spec.Sync.IntervalSeconds = 60 } - // TODO: move this logic into github repository concrete implementation. - if r.Spec.Type == provisioning.GitHubRepositoryType { - if r.Spec.GitHub == nil { - return fmt.Errorf("github configuration is required") - } - - // Trim trailing slash or .git - if len(r.Spec.GitHub.URL) > 5 { - r.Spec.GitHub.URL = strings.TrimSuffix(r.Spec.GitHub.URL, ".git") - r.Spec.GitHub.URL = strings.TrimSuffix(r.Spec.GitHub.URL, "/") - } - } - if r.Spec.Type == provisioning.GitRepositoryType { - if r.Spec.Git == nil { - return fmt.Errorf("git configuration is required") - } - - if r.Spec.GitHub != nil { - return fmt.Errorf("git and github cannot be used together") - } - - if r.Spec.Local != nil { - return fmt.Errorf("git and local cannot be used together") - } - - // Trim trailing slash and ensure .git is present - if len(r.Spec.Git.URL) > 5 { - r.Spec.Git.URL = strings.TrimSuffix(r.Spec.Git.URL, "/") - - if !strings.HasSuffix(r.Spec.Git.URL, ".git") { - r.Spec.Git.URL = r.Spec.Git.URL + ".git" - } - } - } - if r.Spec.Workflows == nil { r.Spec.Workflows = []provisioning.Workflow{} } @@ -596,6 +566,10 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH b.repositoryLister = repoInformer.Lister() + // Create the repository resources factory + usageMetricCollector := usage.MetricCollector(b.tracer, b.repositoryLister, b.unified) + b.usageStats.RegisterMetricsFunc(usageMetricCollector) + stageIfPossible := repository.WrapWithStageAndPushIfPossible exportWorker := export.NewExportWorker( b.clients, @@ -643,7 +617,13 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH b.storageStatus, ) - workers := []jobs.Worker{migrationWorker, syncWorker, exportWorker} + deleteWorker := deletepkg.NewWorker(syncWorker, stageIfPossible) + workers := []jobs.Worker{ + deleteWorker, + exportWorker, + migrationWorker, + syncWorker, + } // Add any extra workers for _, extra := range b.extras { @@ -821,6 +801,15 @@ func (b *APIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, err Required: false, }, }, + { + ParameterProps: spec3.ParameterProps{ + Name: "originalPath", + In: "query", + Description: "path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -> 'c/d.json') or folder-to-folder (e.g., 'some/' -> 'new/')", + Schema: spec.StringProperty(), + Required: false, + }, + }, } sub.Delete.Parameters = comment sub.Post.Parameters = comment diff --git a/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go b/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go index d16a5450326..4b914b50f19 100644 --- a/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go +++ b/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go @@ -451,6 +451,56 @@ func (_c *MockGitRepository_ListRefs_Call) RunAndReturn(run func(context.Context return _c } +// Move provides a mock function with given fields: ctx, oldPath, newPath, ref, message +func (_m *MockGitRepository) Move(ctx context.Context, oldPath string, newPath string, ref string, message string) error { + ret := _m.Called(ctx, oldPath, newPath, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Move") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, oldPath, newPath, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockGitRepository_Move_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Move' +type MockGitRepository_Move_Call struct { + *mock.Call +} + +// Move is a helper method to define mock.On call +// - ctx context.Context +// - oldPath string +// - newPath string +// - ref string +// - message string +func (_e *MockGitRepository_Expecter) Move(ctx interface{}, oldPath interface{}, newPath interface{}, ref interface{}, message interface{}) *MockGitRepository_Move_Call { + return &MockGitRepository_Move_Call{Call: _e.mock.On("Move", ctx, oldPath, newPath, ref, message)} +} + +func (_c *MockGitRepository_Move_Call) Run(run func(ctx context.Context, oldPath string, newPath string, ref string, message string)) *MockGitRepository_Move_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MockGitRepository_Move_Call) Return(_a0 error) *MockGitRepository_Move_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockGitRepository_Move_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockGitRepository_Move_Call { + _c.Call.Return(run) + return _c +} + // OnCreate provides a mock function with given fields: ctx func (_m *MockGitRepository) OnCreate(ctx context.Context) ([]map[string]interface{}, error) { ret := _m.Called(ctx) diff --git a/pkg/registry/apis/provisioning/repository/git/mutator.go b/pkg/registry/apis/provisioning/repository/git/mutator.go index a67f473b82d..e68940fafb8 100644 --- a/pkg/registry/apis/provisioning/repository/git/mutator.go +++ b/pkg/registry/apis/provisioning/repository/git/mutator.go @@ -2,6 +2,8 @@ package git import ( "context" + "fmt" + "strings" "k8s.io/apimachinery/pkg/runtime" @@ -17,10 +19,27 @@ func Mutator(secrets secrets.RepositorySecrets) controller.Mutator { return nil } - if repo.Spec.Git == nil { + if repo.Spec.Type != provisioning.GitRepositoryType { return nil } + if repo.Spec.Git == nil { + return fmt.Errorf("git configuration is required for git repository type") + } + + if repo.Spec.Git.URL != "" { + url := strings.TrimSpace(repo.Spec.Git.URL) + if url != "" { + // Remove any trailing slashes + url = strings.TrimRight(url, "/") + // Only add .git if it's not already present + if !strings.HasSuffix(url, ".git") { + url = url + ".git" + } + repo.Spec.Git.URL = url + } + } + if repo.Spec.Git.Token != "" { secretName := repo.Name + gitTokenSecretSuffix nameOrValue, err := secrets.Encrypt(ctx, repo, secretName, repo.Spec.Git.Token) diff --git a/pkg/registry/apis/provisioning/repository/git/mutator_test.go b/pkg/registry/apis/provisioning/repository/git/mutator_test.go index f924b1b2146..87196d6bcb0 100644 --- a/pkg/registry/apis/provisioning/repository/git/mutator_test.go +++ b/pkg/registry/apis/provisioning/repository/git/mutator_test.go @@ -21,6 +21,7 @@ func TestMutator(t *testing.T) { expectedToken string expectedEncryptedToken string expectedError string + expectedURL string }{ { name: "successful token encryption", @@ -30,6 +31,7 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, Git: &provisioning.GitRepositoryConfig{ Token: "secret-token", }, @@ -44,6 +46,7 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, Git: &provisioning.GitRepositoryConfig{ Token: "secret-token", }, @@ -64,6 +67,7 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, Git: &provisioning.GitRepositoryConfig{ Token: "secret-token", }, @@ -78,6 +82,7 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, Git: &provisioning.GitRepositoryConfig{ Token: "secret-token", }, @@ -97,13 +102,32 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ - Git: nil, + Type: provisioning.LocalRepositoryType, + Git: nil, }, }, setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { // No expectations }, }, + + { + name: "no git spec for git repository type", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: nil, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedError: "git configuration is required for git repository type", + }, { name: "empty token", obj: &provisioning.Repository{ @@ -112,6 +136,7 @@ func TestMutator(t *testing.T) { Namespace: "default", }, Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, Git: &provisioning.GitRepositoryConfig{ Token: "", }, @@ -128,6 +153,101 @@ func TestMutator(t *testing.T) { // No expectations }, }, + { + name: "URL normalization - add .git suffix", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: &provisioning.GitRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedURL: "https://github.com/grafana/grafana.git", + }, + { + name: "URL normalization - keep existing .git suffix", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: &provisioning.GitRepositoryConfig{ + URL: "https://github.com/grafana/grafana.git", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedURL: "https://github.com/grafana/grafana.git", + }, + { + name: "URL normalization - remove trailing slash and add .git", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: &provisioning.GitRepositoryConfig{ + URL: "https://github.com/grafana/grafana/", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedURL: "https://github.com/grafana/grafana.git", + }, + { + name: "URL normalization - trim whitespace and add .git", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: &provisioning.GitRepositoryConfig{ + URL: " https://github.com/grafana/grafana ", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedURL: "https://github.com/grafana/grafana.git", + }, + { + name: "URL normalization - empty URL after trim", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + Git: &provisioning.GitRepositoryConfig{ + URL: " ", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) { + // No expectations + }, + expectedURL: "", + }, } for _, tt := range tests { @@ -152,6 +272,11 @@ func TestMutator(t *testing.T) { // EncryptedToken should be set to the expected value assert.Equal(t, tt.expectedEncryptedToken, string(repo.Spec.Git.EncryptedToken), "EncryptedToken should match expected value") } + + // Check URL normalization + if tt.expectedURL != "" { + assert.Equal(t, tt.expectedURL, repo.Spec.Git.URL, "URL should be normalized correctly") + } } } }) diff --git a/pkg/registry/apis/provisioning/repository/git/repository.go b/pkg/registry/apis/provisioning/repository/git/repository.go index a822b20eb8d..5bcdb12cd94 100644 --- a/pkg/registry/apis/provisioning/repository/git/repository.go +++ b/pkg/registry/apis/provisioning/repository/git/repository.go @@ -457,6 +457,30 @@ func (r *gitRepository) Delete(ctx context.Context, path, ref, comment string) e return r.commitAndPush(ctx, writer, comment) } +func (r *gitRepository) Move(ctx context.Context, oldPath, newPath, ref, comment string) error { + if ref == "" { + ref = r.gitConfig.Branch + } + ctx, _ = r.logger(ctx, ref) + + branchRef, err := r.ensureBranchExists(ctx, ref) + if err != nil { + return err + } + + // Create a staged writer + writer, err := r.client.NewStagedWriter(ctx, branchRef) + if err != nil { + return fmt.Errorf("create staged writer: %w", err) + } + + if err := r.move(ctx, oldPath, newPath, writer); err != nil { + return err + } + + return r.commitAndPush(ctx, writer, comment) +} + func (r *gitRepository) delete(ctx context.Context, path string, writer nanogit.StagedWriter) error { finalPath := safepath.Join(r.gitConfig.Path, path) // Check if it's a directory - use DeleteTree for directories, DeleteBlob for files @@ -480,6 +504,44 @@ func (r *gitRepository) delete(ctx context.Context, path string, writer nanogit. return nil } +func (r *gitRepository) move(ctx context.Context, oldPath, newPath string, writer nanogit.StagedWriter) error { + oldFinalPath := safepath.Join(r.gitConfig.Path, oldPath) + newFinalPath := safepath.Join(r.gitConfig.Path, newPath) + + // Check if moving directories + if safepath.IsDir(oldPath) && safepath.IsDir(newPath) { + // For directories, trim trailing slashes and use MoveTree + oldTrimmed := strings.TrimSuffix(oldFinalPath, "/") + newTrimmed := strings.TrimSuffix(newFinalPath, "/") + + if _, err := writer.MoveTree(ctx, oldTrimmed, newTrimmed); err != nil { + if errors.Is(err, nanogit.ErrObjectNotFound) { + return repository.ErrFileNotFound + } + if errors.Is(err, nanogit.ErrObjectAlreadyExists) { + return repository.ErrFileAlreadyExists + } + return fmt.Errorf("move tree: %w", err) + } + } else if !safepath.IsDir(oldPath) && !safepath.IsDir(newPath) { + // For files, use MoveBlob operation + if _, err := writer.MoveBlob(ctx, oldFinalPath, newFinalPath); err != nil { + if errors.Is(err, nanogit.ErrObjectNotFound) { + return repository.ErrFileNotFound + } + if errors.Is(err, nanogit.ErrObjectAlreadyExists) { + return repository.ErrFileAlreadyExists + } + return fmt.Errorf("move blob: %w", err) + } + } else { + // Mismatched types (file to directory or vice versa) + return apierrors.NewBadRequest("cannot move between file and directory types") + } + + return nil +} + func (r *gitRepository) History(_ context.Context, _ string, _ string) ([]provisioning.HistoryItem, error) { return nil, &apierrors.StatusError{ErrStatus: metav1.Status{ Status: metav1.StatusFailure, diff --git a/pkg/registry/apis/provisioning/repository/git/repository_test.go b/pkg/registry/apis/provisioning/repository/git/repository_test.go index 0f13c2f9854..f70346825f6 100644 --- a/pkg/registry/apis/provisioning/repository/git/repository_test.go +++ b/pkg/registry/apis/provisioning/repository/git/repository_test.go @@ -2256,7 +2256,7 @@ func TestGitRepository_Stage(t *testing.T) { t.Run("calls NewStagedGitRepository", func(t *testing.T) { ctx := context.Background() opts := repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, } // Since NewStagedGitRepository is not mocked and may panic, we expect this to fail @@ -3244,7 +3244,7 @@ func TestGitRepository_CompareFiles_EmptyBase(t *testing.T) { // Verify CompareCommits was called with empty base hash and feature hash require.Equal(t, 1, mockClient.CompareCommitsCallCount()) _, baseHash, refHash := mockClient.CompareCommitsArgsForCall(0) - require.Equal(t, hash.Zero, baseHash) // Empty hash for empty base + require.Equal(t, hash.Zero, baseHash) // Empty base should be zero hash require.Equal(t, hash.MustFromHex("0102030405060708090a0b0c0d0e0f1011121314"), refHash) } @@ -3812,3 +3812,527 @@ func TestGitRepository_OnDelete(t *testing.T) { }) } } + +func TestGitRepository_Move(t *testing.T) { + tests := []struct { + name string + oldPath string + newPath string + ref string + comment string + setupMock func(*mocks.FakeClient) + expectedError string + }{ + { + name: "successful move", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("1234567890abcdef1234567890abcdef12345678") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob - returns the hash of the moved blob + movedHash, _ := hash.FromHex("abcdef1234567890abcdef1234567890abcdef12") + mockWriter.MoveBlobReturns(movedHash, nil) + + // Mock commit and push + commitHash, _ := hash.FromHex("fedcba0987654321fedcba0987654321fedcba09") + mockWriter.CommitReturns(&nanogit.Commit{Hash: commitHash}, nil) + mockWriter.PushReturns(nil) + }, + }, + { + name: "move with empty ref uses default branch", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "", + comment: "move file", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior for default branch + refHash, _ := hash.FromHex("aaaa1111bbbb2222cccc3333dddd4444eeee5555") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob + moveHash, _ := hash.FromHex("bbbb2222cccc3333dddd4444eeee5555ffff6666") + mockWriter.MoveBlobReturns(moveHash, nil) + + // Mock commit and push + commitHash, _ := hash.FromHex("cccc3333dddd4444eeee5555ffff6666aaaa1111") + mockWriter.CommitReturns(&nanogit.Commit{Hash: commitHash}, nil) + mockWriter.PushReturns(nil) + }, + }, + { + name: "successful directory move", + oldPath: "old/", + newPath: "new/", + ref: "main", + comment: "move directory", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("dddd4444eeee5555ffff6666aaaa1111bbbb2222") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveTree (for directories, we trim trailing slashes) + treeHash, _ := hash.FromHex("eeee5555ffff6666aaaa1111bbbb2222cccc3333") + mockWriter.MoveTreeReturns(treeHash, nil) + + // Mock commit and push + commitHash, _ := hash.FromHex("ffff6666aaaa1111bbbb2222cccc3333dddd4444") + mockWriter.CommitReturns(&nanogit.Commit{Hash: commitHash}, nil) + mockWriter.PushReturns(nil) + }, + }, + { + name: "move file to directory type should fail", + oldPath: "file.yaml", + newPath: "directory/", + ref: "main", + comment: "move file to directory", + expectedError: "cannot move between file and directory types", + setupMock: func(mockClient *mocks.FakeClient) { + // No mocks needed as this should fail early during validation + }, + }, + { + name: "move non-existent file", + oldPath: "nonexistent.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move missing file", + expectedError: "file not found", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("aaaa0000bbbb1111cccc2222dddd3333eeee4444") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob to return not found error + mockWriter.MoveBlobReturns(hash.Hash{}, nanogit.ErrObjectNotFound) + }, + }, + { + name: "move to existing file should fail", + oldPath: "old.yaml", + newPath: "existing.yaml", + ref: "main", + comment: "move to existing", + expectedError: "file already exists", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("ffff0000eeee1111dddd2222cccc3333bbbb4444") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob to return already exists error + mockWriter.MoveBlobReturns(hash.Hash{}, nanogit.ErrObjectAlreadyExists) + }, + }, + { + name: "branch creation fails", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "nonexistent-branch", + comment: "move on nonexistent branch", + expectedError: "get source branch ref", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock branch not found + mockClient.GetRefReturnsOnCall(0, nanogit.Ref{}, nanogit.ErrObjectNotFound) + // Mock getting source branch for creation - also fails + mockClient.GetRefReturnsOnCall(1, nanogit.Ref{}, nanogit.ErrObjectNotFound) + }, + }, + { + name: "staged writer creation fails", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + expectedError: "create staged writer", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("1111aaaa2222bbbb3333cccc4444dddd5555eeee") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter failure + mockClient.NewStagedWriterReturns(nil, errors.New("writer creation failed")) + }, + }, + { + name: "commit fails", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + expectedError: "commit changes", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("2222bbbb3333cccc4444dddd5555eeee6666ffff") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob success + moveHash, _ := hash.FromHex("3333cccc4444dddd5555eeee6666ffff7777aaaa") + mockWriter.MoveBlobReturns(moveHash, nil) + + // Mock commit failure + mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) + }, + }, + { + name: "push fails", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + expectedError: "push changes", + setupMock: func(mockClient *mocks.FakeClient) { + // Mock ensureBranchExists behavior + refHash, _ := hash.FromHex("4444dddd5555eeee6666ffff7777aaaa8888bbbb") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + // Mock NewStagedWriter + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + + // Mock MoveBlob success + moveHash, _ := hash.FromHex("5555eeee6666ffff7777aaaa8888bbbb9999cccc") + mockWriter.MoveBlobReturns(moveHash, nil) + + // Mock commit success + commitHash, _ := hash.FromHex("6666ffff7777aaaa8888bbbb9999cccc0000dddd") + mockWriter.CommitReturns(&nanogit.Commit{Hash: commitHash}, nil) + + // Mock push failure + mockWriter.PushReturns(errors.New("push failed")) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock + mockClient := &mocks.FakeClient{} + tt.setupMock(mockClient) + + // Create repository config + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + }, + } + + gitConfig := RepositoryConfig{ + URL: "https://github.com/example/repo.git", + Branch: "main", + Token: "token123", + Path: "configs", + } + + gitRepo := &gitRepository{ + config: config, + gitConfig: gitConfig, + client: mockClient, + } + + // Execute move operation + err := gitRepo.Move(context.Background(), tt.oldPath, tt.newPath, tt.ref, tt.comment) + + // Verify results + if tt.expectedError != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedError) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGitRepository_Move_ErrorConditions(t *testing.T) { + tests := []struct { + name string + oldPath string + newPath string + ref string + comment string + setupMock func(*mocks.FakeClient) + expectedError string + errorType error + }{ + { + name: "MoveBlob - ErrObjectNotFound", + oldPath: "missing.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move missing file", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("1234567890abcdef1234567890abcdef12345678") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveBlobReturns(hash.Hash{}, nanogit.ErrObjectNotFound) + }, + expectedError: "file not found", + errorType: repository.ErrFileNotFound, + }, + { + name: "MoveBlob - ErrObjectAlreadyExists", + oldPath: "old.yaml", + newPath: "existing.yaml", + ref: "main", + comment: "move to existing file", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("abcdef1234567890abcdef1234567890abcdef12") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveBlobReturns(hash.Hash{}, nanogit.ErrObjectAlreadyExists) + }, + expectedError: "file already exists", + errorType: repository.ErrFileAlreadyExists, + }, + { + name: "MoveBlob - generic error", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file with generic error", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("fedcba0987654321fedcba0987654321fedcba09") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveBlobReturns(hash.Hash{}, errors.New("network error")) + }, + expectedError: "move blob: network error", + }, + { + name: "MoveTree - ErrObjectNotFound", + oldPath: "missing-dir/", + newPath: "new-dir/", + ref: "main", + comment: "move missing directory", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("1111222233334444555566667777888899990000") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveTreeReturns(hash.Hash{}, nanogit.ErrObjectNotFound) + }, + expectedError: "file not found", + errorType: repository.ErrFileNotFound, + }, + { + name: "MoveTree - ErrObjectAlreadyExists", + oldPath: "old-dir/", + newPath: "existing-dir/", + ref: "main", + comment: "move to existing directory", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("aaaa1111bbbb2222cccc3333dddd4444eeee5555") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveTreeReturns(hash.Hash{}, nanogit.ErrObjectAlreadyExists) + }, + expectedError: "file already exists", + errorType: repository.ErrFileAlreadyExists, + }, + { + name: "MoveTree - generic error", + oldPath: "old-dir/", + newPath: "new-dir/", + ref: "main", + comment: "move directory with generic error", + setupMock: func(mockClient *mocks.FakeClient) { + refHash, _ := hash.FromHex("ffff6666eeee5555dddd4444cccc3333bbbb2222") + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + mockWriter.MoveTreeReturns(hash.Hash{}, errors.New("permission denied")) + }, + expectedError: "move tree: permission denied", + }, + { + name: "invalid branch name", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "invalid//branch", + comment: "move with invalid branch", + setupMock: func(mockClient *mocks.FakeClient) { + // No mock setup needed as error is caught during branch validation + }, + expectedError: "invalid branch name", + }, + { + name: "ensure branch exists fails - source branch not found", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "new-branch", + comment: "move to new branch when source doesn't exist", + setupMock: func(mockClient *mocks.FakeClient) { + // First call - new branch doesn't exist + mockClient.GetRefReturnsOnCall(0, nanogit.Ref{}, nanogit.ErrObjectNotFound) + // Second call - source branch also doesn't exist + mockClient.GetRefReturnsOnCall(1, nanogit.Ref{}, nanogit.ErrObjectNotFound) + }, + expectedError: "get source branch ref", + }, + { + name: "ensure branch exists fails - branch creation error", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "new-branch", + comment: "move to new branch with creation failure", + setupMock: func(mockClient *mocks.FakeClient) { + // First call - new branch doesn't exist + mockClient.GetRefReturnsOnCall(0, nanogit.Ref{}, nanogit.ErrObjectNotFound) + // Second call - get source branch succeeds + refHash, _ := hash.FromHex("7777aaaa8888bbbb9999cccc0000dddd1111eeee") + mockClient.GetRefReturnsOnCall(1, nanogit.Ref{ + Name: "refs/heads/main", + Hash: refHash, + }, nil) + // CreateRef fails + mockClient.CreateRefReturns(errors.New("create ref failed")) + }, + expectedError: "create branch", + }, + { + name: "directory to file move type mismatch", + oldPath: "directory/", + newPath: "file.yaml", + ref: "main", + comment: "move directory to file", + setupMock: func(mockClient *mocks.FakeClient) { + // No mock setup needed as error is caught during validation + }, + expectedError: "cannot move between file and directory types", + }, + { + name: "file to directory move type mismatch", + oldPath: "file.yaml", + newPath: "directory/", + ref: "main", + comment: "move file to directory", + setupMock: func(mockClient *mocks.FakeClient) { + // No mock setup needed as error is caught during validation + }, + expectedError: "cannot move between file and directory types", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := &mocks.FakeClient{} + tt.setupMock(mockClient) + + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitRepositoryType, + }, + } + + gitConfig := RepositoryConfig{ + URL: "https://github.com/example/repo.git", + Branch: "main", + Token: "token123", + Path: "configs", + } + + gitRepo := &gitRepository{ + config: config, + gitConfig: gitConfig, + client: mockClient, + } + + err := gitRepo.Move(context.Background(), tt.oldPath, tt.newPath, tt.ref, tt.comment) + + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedError) + + if tt.errorType != nil { + require.ErrorIs(t, err, tt.errorType) + } + }) + } +} diff --git a/pkg/registry/apis/provisioning/repository/git/staged.go b/pkg/registry/apis/provisioning/repository/git/staged.go index e11044230b2..335193716b0 100644 --- a/pkg/registry/apis/provisioning/repository/git/staged.go +++ b/pkg/registry/apis/provisioning/repository/git/staged.go @@ -26,7 +26,12 @@ func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repos defer cancel() } - ref, err := repo.client.GetRef(ctx, "refs/heads/"+repo.gitConfig.Branch) + branch := opts.Ref + if branch == "" { + branch = repo.gitConfig.Branch + } + + ref, err := repo.client.GetRef(ctx, "refs/heads/"+branch) if err != nil { // TODO: opts.CreateIfNotExists doesn't make sense in the context of the staged repository // because we only support the branch that is passed in. @@ -47,8 +52,25 @@ func NewStagedGitRepository(ctx context.Context, repo *gitRepository, opts repos }, nil } +// isRefSupported checks if the given ref is supported for staged operations. +// It returns true if ref is empty, equals the git config branch, or equals the staged options ref. +func (r *stagedGitRepository) isRefSupported(ref string) bool { + if ref == "" { + return true + } + if ref == r.gitConfig.Branch { + return true + } + // Allow ref if it matches the staged options ref (the branch we're staging to) + stagingBranch := r.opts.Ref + if stagingBranch == "" { + stagingBranch = r.gitConfig.Branch + } + return ref == stagingBranch +} + func (r *stagedGitRepository) Read(ctx context.Context, path, ref string) (*repository.FileInfo, error) { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return nil, errors.New("ref is not supported for staged repository") } @@ -58,7 +80,7 @@ func (r *stagedGitRepository) Read(ctx context.Context, path, ref string) (*repo } func (r *stagedGitRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return nil, errors.New("ref is not supported for staged repository") } @@ -69,8 +91,27 @@ func (r *stagedGitRepository) ReadTree(ctx context.Context, ref string) ([]repos return r.gitRepository.ReadTree(ctx, ref) } +// handleCommitAndPush handles the commit and push logic based on the StageMode +func (r *stagedGitRepository) handleCommitAndPush(ctx context.Context, message string) error { + switch r.opts.Mode { + case repository.StageModeCommitOnEach: + return r.commit(ctx, r.writer, message) + case repository.StageModeCommitAndPushOnEach: + if err := r.commit(ctx, r.writer, message); err != nil { + return err + } + return r.Push(ctx) + case repository.StageModeCommitOnlyOnce: + // No immediate commit, will commit on Push + return nil + default: + // Default to StageModeCommitOnEach for backward compatibility + return r.commit(ctx, r.writer, message) + } +} + func (r *stagedGitRepository) Create(ctx context.Context, path, ref string, data []byte, message string) error { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return errors.New("ref is not supported for staged repository") } @@ -78,15 +119,7 @@ func (r *stagedGitRepository) Create(ctx context.Context, path, ref string, data return err } - if err := r.commit(ctx, r.writer, message); err != nil { - return err - } - - if r.opts.PushOnWrites { - return r.Push(ctx) - } - - return nil + return r.handleCommitAndPush(ctx, message) } func (r *stagedGitRepository) blobExists(ctx context.Context, path string) (bool, error) { @@ -97,7 +130,7 @@ func (r *stagedGitRepository) blobExists(ctx context.Context, path string) (bool } func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data []byte, message string) error { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return errors.New("ref is not supported for staged repository") } @@ -116,19 +149,11 @@ func (r *stagedGitRepository) Write(ctx context.Context, path, ref string, data } } - if err := r.commit(ctx, r.writer, message); err != nil { - return err - } - - if r.opts.PushOnWrites { - return r.Push(ctx) - } - - return nil + return r.handleCommitAndPush(ctx, message) } func (r *stagedGitRepository) Update(ctx context.Context, path, ref string, data []byte, message string) error { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return errors.New("ref is not supported for staged repository") } @@ -140,19 +165,11 @@ func (r *stagedGitRepository) Update(ctx context.Context, path, ref string, data return err } - if err := r.commit(ctx, r.writer, message); err != nil { - return err - } - - if r.opts.PushOnWrites { - return r.Push(ctx) - } - - return nil + return r.handleCommitAndPush(ctx, message) } func (r *stagedGitRepository) Delete(ctx context.Context, path, ref, message string) error { - if ref != "" && ref != r.gitConfig.Branch { + if !r.isRefSupported(ref) { return errors.New("ref is not supported for staged repository") } @@ -160,15 +177,7 @@ func (r *stagedGitRepository) Delete(ctx context.Context, path, ref, message str return err } - if err := r.commit(ctx, r.writer, message); err != nil { - return err - } - - if r.opts.PushOnWrites { - return r.Push(ctx) - } - - return nil + return r.handleCommitAndPush(ctx, message) } func (r *stagedGitRepository) Push(ctx context.Context) error { @@ -178,6 +187,16 @@ func (r *stagedGitRepository) Push(ctx context.Context) error { defer cancel() } + if r.opts.Mode == repository.StageModeCommitOnlyOnce { + message := r.opts.CommitOnlyOnceMessage + if message == "" { + message = "Staged changes" + } + if err := r.commit(ctx, r.writer, message); err != nil { + return err + } + } + return r.writer.Push(ctx) } diff --git a/pkg/registry/apis/provisioning/repository/git/staged_test.go b/pkg/registry/apis/provisioning/repository/git/staged_test.go index 9dc502cf8c2..0a614b94c57 100644 --- a/pkg/registry/apis/provisioning/repository/git/staged_test.go +++ b/pkg/registry/apis/provisioning/repository/git/staged_test.go @@ -3,6 +3,7 @@ package git import ( "context" "errors" + "strings" "testing" "time" @@ -16,10 +17,11 @@ import ( func TestNewStagedGitRepository(t *testing.T) { tests := []struct { - name string - setupMock func(*mocks.FakeClient) - opts repository.StageOptions - wantError error + name string + setupMock func(*mocks.FakeClient) + opts repository.StageOptions + wantError error + expectedRef string }{ { name: "succeeds with default options", @@ -31,7 +33,25 @@ func TestNewStagedGitRepository(t *testing.T) { mockWriter := &mocks.FakeStagedWriter{} mockClient.NewStagedWriterReturns(mockWriter, nil) }, + expectedRef: "refs/heads/main", opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnEach, + }, + wantError: nil, + }, + { + name: "succeeds with custom ref option", + setupMock: func(mockClient *mocks.FakeClient) { + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/custom", + Hash: hash.Hash{1, 2, 3}, + }, nil) + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + }, + expectedRef: "refs/heads/custom", + opts: repository.StageOptions{ + Ref: "custom", PushOnWrites: false, }, wantError: nil, @@ -47,9 +67,10 @@ func TestNewStagedGitRepository(t *testing.T) { mockClient.NewStagedWriterReturns(mockWriter, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, - wantError: nil, + wantError: nil, + expectedRef: "refs/heads/main", }, { name: "succeeds with timeout", @@ -62,10 +83,44 @@ func TestNewStagedGitRepository(t *testing.T) { mockClient.NewStagedWriterReturns(mockWriter, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, - Timeout: time.Second * 5, + Mode: repository.StageModeCommitOnEach, + Timeout: time.Second * 5, }, - wantError: nil, + expectedRef: "refs/heads/main", + wantError: nil, + }, + { + name: "succeeds with CommitOnlyOnce option", + setupMock: func(mockClient *mocks.FakeClient) { + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: hash.Hash{1, 2, 3}, + }, nil) + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + }, + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + CommitOnlyOnceMessage: "Custom commit message", + }, + expectedRef: "refs/heads/main", + wantError: nil, + }, + { + name: "succeeds with CommitAndPushOnEach option", + setupMock: func(mockClient *mocks.FakeClient) { + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/main", + Hash: hash.Hash{1, 2, 3}, + }, nil) + mockWriter := &mocks.FakeStagedWriter{} + mockClient.NewStagedWriterReturns(mockWriter, nil) + }, + opts: repository.StageOptions{ + Mode: repository.StageModeCommitAndPushOnEach, + }, + expectedRef: "refs/heads/main", + wantError: nil, }, { name: "fails with GetRef error", @@ -73,7 +128,7 @@ func TestNewStagedGitRepository(t *testing.T) { mockClient.GetRefReturns(nanogit.Ref{}, errors.New("ref not found")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, wantError: errors.New("ref not found"), }, @@ -87,9 +142,10 @@ func TestNewStagedGitRepository(t *testing.T) { mockClient.NewStagedWriterReturns(nil, errors.New("failed to create writer")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, - wantError: errors.New("build staged writer: failed to create writer"), + wantError: errors.New("build staged writer: failed to create writer"), + expectedRef: "refs/heads/main", }, } @@ -121,8 +177,13 @@ func TestNewStagedGitRepository(t *testing.T) { // Compare opts fields individually since function pointers can't be compared directly actualOpts := stagedRepo.(*stagedGitRepository).opts - require.Equal(t, tt.opts.PushOnWrites, actualOpts.PushOnWrites) + require.Equal(t, tt.opts.Mode, actualOpts.Mode) require.Equal(t, tt.opts.Timeout, actualOpts.Timeout) + require.Equal(t, tt.opts.CommitOnlyOnceMessage, actualOpts.CommitOnlyOnceMessage) + + // Verify the expected ref + _, ref := mockClient.GetRefArgsForCall(0) + require.Equal(t, tt.expectedRef, ref) } }) } @@ -174,6 +235,25 @@ func TestStagedGitRepository_Read(t *testing.T) { ref: "main", wantError: nil, }, + { + name: "succeeds with ref matching stage options", + setupMock: func(mockClient *mocks.FakeClient) { + mockClient.GetRefReturns(nanogit.Ref{ + Name: "refs/heads/feature", + Hash: hash.Hash{1, 2, 3}, + }, nil) + mockClient.GetCommitReturns(&nanogit.Commit{ + Tree: hash.Hash{4, 5, 6}, + }, nil) + mockClient.GetBlobByPathReturns(&nanogit.Blob{ + Content: []byte("file content"), + Hash: hash.Hash{7, 8, 9}, + }, nil) + }, + path: "test.yaml", + ref: "feature", + wantError: nil, + }, { name: "fails with unsupported ref", setupMock: func(_ *mocks.FakeClient) { @@ -190,7 +270,13 @@ func TestStagedGitRepository_Read(t *testing.T) { mockClient := &mocks.FakeClient{} tt.setupMock(mockClient) - stagedRepo := createTestStagedRepository(mockClient) + // Use stage options with ref "feature" for the specific test case + opts := repository.StageOptions{} + if tt.ref == "feature" { + opts.Ref = "feature" + } + + stagedRepo := createTestStagedRepositoryWithWriter(&mocks.FakeStagedWriter{}, opts, mockClient) fileInfo, err := stagedRepo.Read(context.Background(), tt.path, tt.ref) if tt.wantError != nil { require.EqualError(t, err, tt.wantError.Error()) @@ -277,7 +363,7 @@ func TestStagedGitRepository_Create(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -294,7 +380,7 @@ func TestStagedGitRepository_Create(t *testing.T) { mockWriter.PushReturns(nil) }, opts: repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, }, path: "test.yaml", ref: "main", @@ -309,7 +395,7 @@ func TestStagedGitRepository_Create(t *testing.T) { // No setup needed as error occurs before writer calls }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "feature-branch", @@ -323,7 +409,7 @@ func TestStagedGitRepository_Create(t *testing.T) { mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create blob failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -338,7 +424,7 @@ func TestStagedGitRepository_Create(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -354,7 +440,7 @@ func TestStagedGitRepository_Create(t *testing.T) { mockWriter.PushReturns(errors.New("push failed")) }, opts: repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, }, path: "test.yaml", ref: "", @@ -363,6 +449,21 @@ func TestStagedGitRepository_Create(t *testing.T) { wantError: errors.New("push failed"), expectPush: true, // Push is still called even though it fails }, + { + name: "succeeds with CommitOnlyOnce - no immediate commit", + setupMock: func(mockWriter *mocks.FakeStagedWriter) { + mockWriter.CreateBlobReturns(hash.Hash{1, 2, 3}, nil) + }, + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + }, + path: "test.yaml", + ref: "", + data: []byte("content"), + message: "Create test file", + wantError: nil, + expectPush: false, + }, } for _, tt := range tests { @@ -386,6 +487,28 @@ func TestStagedGitRepository_Create(t *testing.T) { } else if tt.wantError == nil { require.Equal(t, 0, mockWriter.PushCallCount()) } + + // Verify commit behavior based on StageMode + switch tt.opts.Mode { + case repository.StageModeCommitOnlyOnce: + require.Equal(t, 0, mockWriter.CommitCallCount(), "No commits should be made when StageModeCommitOnlyOnce is used") + case repository.StageModeCommitOnEach, repository.StageModeCommitAndPushOnEach: + if tt.wantError == nil || strings.Contains(tt.wantError.Error(), "push failed") { + require.Equal(t, 1, mockWriter.CommitCallCount(), "One commit should be made when using commit modes (even if push fails)") + } else if tt.wantError != nil && strings.Contains(tt.wantError.Error(), "commit") { + // Commit failed, so it should have been attempted but failed + require.Equal(t, 1, mockWriter.CommitCallCount(), "Commit should be attempted even if it fails") + } else if tt.wantError != nil { + require.Equal(t, 0, mockWriter.CommitCallCount(), "No commits should be made when error occurs before commit") + } + default: + // Default behavior (backward compatibility) + if tt.wantError == nil || strings.Contains(tt.wantError.Error(), "push failed") { + require.Equal(t, 1, mockWriter.CommitCallCount(), "One commit should be made with default mode (even if push fails)") + } else if tt.wantError != nil { + require.Equal(t, 0, mockWriter.CommitCallCount(), "No commits should be made when error occurs before commit") + } + } }) } } @@ -411,7 +534,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -430,7 +553,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.PushReturns(nil) }, opts: repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, }, path: "test.yaml", ref: "main", @@ -446,7 +569,7 @@ func TestStagedGitRepository_Write(t *testing.T) { // No setup needed as error occurs before writer calls }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "feature-branch", @@ -460,7 +583,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.BlobExistsReturns(false, errors.New("blob exists check failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -475,7 +598,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.CreateBlobReturns(hash.Hash{}, errors.New("create failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -490,7 +613,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -506,7 +629,7 @@ func TestStagedGitRepository_Write(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -560,7 +683,7 @@ func TestStagedGitRepository_Update(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -577,7 +700,7 @@ func TestStagedGitRepository_Update(t *testing.T) { mockWriter.PushReturns(nil) }, opts: repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, }, path: "test.yaml", ref: "main", @@ -592,7 +715,7 @@ func TestStagedGitRepository_Update(t *testing.T) { // No setup needed as error occurs before writer calls }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "feature-branch", @@ -606,7 +729,7 @@ func TestStagedGitRepository_Update(t *testing.T) { // No setup needed as error occurs before writer calls }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "directory/", ref: "", @@ -620,7 +743,7 @@ func TestStagedGitRepository_Update(t *testing.T) { mockWriter.UpdateBlobReturns(hash.Hash{}, errors.New("update blob failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -635,7 +758,7 @@ func TestStagedGitRepository_Update(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -688,7 +811,7 @@ func TestStagedGitRepository_Delete(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, nil) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -704,7 +827,7 @@ func TestStagedGitRepository_Delete(t *testing.T) { mockWriter.PushReturns(nil) }, opts: repository.StageOptions{ - PushOnWrites: true, + Mode: repository.StageModeCommitAndPushOnEach, }, path: "testdir/", ref: "main", @@ -718,7 +841,7 @@ func TestStagedGitRepository_Delete(t *testing.T) { // No setup needed as error occurs before writer calls }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "feature-branch", @@ -731,7 +854,7 @@ func TestStagedGitRepository_Delete(t *testing.T) { mockWriter.DeleteBlobReturns(hash.Hash{}, errors.New("delete blob failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -745,7 +868,7 @@ func TestStagedGitRepository_Delete(t *testing.T) { mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) }, opts: repository.StageOptions{ - PushOnWrites: false, + Mode: repository.StageModeCommitOnEach, }, path: "test.yaml", ref: "", @@ -781,42 +904,96 @@ func TestStagedGitRepository_Delete(t *testing.T) { func TestStagedGitRepository_Push(t *testing.T) { tests := []struct { - name string - setupMock func(*mocks.FakeStagedWriter) - wantError error - expectCalls int + name string + opts repository.StageOptions + setupMock func(*mocks.FakeStagedWriter) + wantError error + expectPushCalls int + expectCommitCalls int }{ { - name: "succeeds with empty ref", + name: "succeeds with normal push", + opts: repository.StageOptions{}, setupMock: func(mockWriter *mocks.FakeStagedWriter) { mockWriter.PushReturns(nil) }, - wantError: nil, - expectCalls: 1, - }, - { - name: "succeeds with matching ref", - setupMock: func(mockWriter *mocks.FakeStagedWriter) { - mockWriter.PushReturns(nil) - }, - wantError: nil, - expectCalls: 1, + wantError: nil, + expectPushCalls: 1, + expectCommitCalls: 0, }, { name: "succeeds with timeout", + opts: repository.StageOptions{ + Timeout: time.Second * 5, + }, setupMock: func(mockWriter *mocks.FakeStagedWriter) { mockWriter.PushReturns(nil) }, - wantError: nil, - expectCalls: 1, + wantError: nil, + expectPushCalls: 1, + expectCommitCalls: 0, + }, + { + name: "succeeds with CommitOnlyOnce and default message", + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + }, + setupMock: func(mockWriter *mocks.FakeStagedWriter) { + mockWriter.CommitReturns(&nanogit.Commit{}, nil) + mockWriter.PushReturns(nil) + }, + wantError: nil, + expectPushCalls: 1, + expectCommitCalls: 1, + }, + { + name: "succeeds with CommitOnlyOnce and custom message", + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + CommitOnlyOnceMessage: "Custom commit message", + }, + setupMock: func(mockWriter *mocks.FakeStagedWriter) { + mockWriter.CommitReturns(&nanogit.Commit{}, nil) + mockWriter.PushReturns(nil) + }, + wantError: nil, + expectPushCalls: 1, + expectCommitCalls: 1, + }, + { + name: "fails with commit error when CommitOnlyOnce", + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + }, + setupMock: func(mockWriter *mocks.FakeStagedWriter) { + mockWriter.CommitReturns(&nanogit.Commit{}, errors.New("commit failed")) + }, + wantError: errors.New("commit changes: commit failed"), + expectPushCalls: 0, + expectCommitCalls: 1, }, { name: "fails with push error", + opts: repository.StageOptions{}, setupMock: func(mockWriter *mocks.FakeStagedWriter) { mockWriter.PushReturns(errors.New("push failed")) }, - wantError: errors.New("push failed"), - expectCalls: 1, + wantError: errors.New("push failed"), + expectPushCalls: 1, + expectCommitCalls: 0, + }, + { + name: "fails with push error after successful commit", + opts: repository.StageOptions{ + Mode: repository.StageModeCommitOnlyOnce, + }, + setupMock: func(mockWriter *mocks.FakeStagedWriter) { + mockWriter.CommitReturns(&nanogit.Commit{}, nil) + mockWriter.PushReturns(errors.New("push failed")) + }, + wantError: errors.New("push failed"), + expectPushCalls: 1, + expectCommitCalls: 1, }, } @@ -825,7 +1002,7 @@ func TestStagedGitRepository_Push(t *testing.T) { mockWriter := &mocks.FakeStagedWriter{} tt.setupMock(mockWriter) - stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, repository.StageOptions{}) + stagedRepo := createTestStagedRepositoryWithWriter(mockWriter, tt.opts) err := stagedRepo.Push(context.Background()) @@ -835,7 +1012,18 @@ func TestStagedGitRepository_Push(t *testing.T) { require.NoError(t, err) } - require.Equal(t, tt.expectCalls, mockWriter.PushCallCount()) + require.Equal(t, tt.expectPushCalls, mockWriter.PushCallCount()) + require.Equal(t, tt.expectCommitCalls, mockWriter.CommitCallCount()) + + // Verify commit message when CommitOnlyOnce is used + if tt.opts.Mode == repository.StageModeCommitOnlyOnce && tt.expectCommitCalls > 0 && tt.wantError == nil { + _, actualMessage, _, _ := mockWriter.CommitArgsForCall(0) + expectedMessage := tt.opts.CommitOnlyOnceMessage + if expectedMessage == "" { + expectedMessage = "Staged changes" + } + require.Equal(t, expectedMessage, actualMessage) + } }) } } @@ -851,6 +1039,75 @@ func TestStagedGitRepository_Remove(t *testing.T) { }) } +func TestStagedGitRepository_isRefSupported(t *testing.T) { + tests := []struct { + name string + stageOpts repository.StageOptions + gitBranch string + ref string + expected bool + }{ + { + name: "empty ref is supported", + stageOpts: repository.StageOptions{}, + gitBranch: "main", + ref: "", + expected: true, + }, + { + name: "ref matches git config branch", + stageOpts: repository.StageOptions{}, + gitBranch: "main", + ref: "main", + expected: true, + }, + { + name: "ref matches stage options ref", + stageOpts: repository.StageOptions{Ref: "feature"}, + gitBranch: "main", + ref: "feature", + expected: true, + }, + { + name: "ref matches stage options ref when empty defaults to git branch", + stageOpts: repository.StageOptions{Ref: ""}, + gitBranch: "main", + ref: "main", + expected: true, + }, + { + name: "unsupported ref", + stageOpts: repository.StageOptions{Ref: "feature"}, + gitBranch: "main", + ref: "other-branch", + expected: false, + }, + { + name: "unsupported ref with empty stage options", + stageOpts: repository.StageOptions{}, + gitBranch: "main", + ref: "feature", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stagedRepo := &stagedGitRepository{ + gitRepository: &gitRepository{ + gitConfig: RepositoryConfig{ + Branch: tt.gitBranch, + }, + }, + opts: tt.stageOpts, + } + + result := stagedRepo.isRefSupported(tt.ref) + require.Equal(t, tt.expected, result) + }) + } +} + // Helper functions for creating test instances func createTestStagedRepository(mockClient *mocks.FakeClient) *stagedGitRepository { diff --git a/pkg/registry/apis/provisioning/repository/github/factory.go b/pkg/registry/apis/provisioning/repository/github/factory.go index da2d50a6131..6f2435ee8d0 100644 --- a/pkg/registry/apis/provisioning/repository/github/factory.go +++ b/pkg/registry/apis/provisioning/repository/github/factory.go @@ -12,6 +12,7 @@ import ( // It exists only for the ability to test the code easily. type Factory struct { // Client allows overriding the client to use in the GH client returned. It exists primarily for testing. + // FIXME: we should replace in this way. We should add some options pattern for the factory. Client *http.Client } @@ -28,7 +29,7 @@ func (r *Factory) New(ctx context.Context, ghToken string) Client { &oauth2.Token{AccessToken: ghToken}, ) - if len(ghToken) == 0 { + if len(ghToken) > 0 { tokenClient := oauth2.NewClient(ctx, tokenSrc) return NewClient(github.NewClient(tokenClient)) } diff --git a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go index a2c2798819a..5ccd5d2e319 100644 --- a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go +++ b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go @@ -453,6 +453,56 @@ func (_c *MockGithubRepository_ListRefs_Call) RunAndReturn(run func(context.Cont return _c } +// Move provides a mock function with given fields: ctx, oldPath, newPath, ref, message +func (_m *MockGithubRepository) Move(ctx context.Context, oldPath string, newPath string, ref string, message string) error { + ret := _m.Called(ctx, oldPath, newPath, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Move") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, oldPath, newPath, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockGithubRepository_Move_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Move' +type MockGithubRepository_Move_Call struct { + *mock.Call +} + +// Move is a helper method to define mock.On call +// - ctx context.Context +// - oldPath string +// - newPath string +// - ref string +// - message string +func (_e *MockGithubRepository_Expecter) Move(ctx interface{}, oldPath interface{}, newPath interface{}, ref interface{}, message interface{}) *MockGithubRepository_Move_Call { + return &MockGithubRepository_Move_Call{Call: _e.mock.On("Move", ctx, oldPath, newPath, ref, message)} +} + +func (_c *MockGithubRepository_Move_Call) Run(run func(ctx context.Context, oldPath string, newPath string, ref string, message string)) *MockGithubRepository_Move_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MockGithubRepository_Move_Call) Return(_a0 error) *MockGithubRepository_Move_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockGithubRepository_Move_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockGithubRepository_Move_Call { + _c.Call.Return(run) + return _c +} + // OnCreate provides a mock function with given fields: ctx func (_m *MockGithubRepository) OnCreate(ctx context.Context) ([]map[string]interface{}, error) { ret := _m.Called(ctx) diff --git a/pkg/registry/apis/provisioning/repository/github/mutator.go b/pkg/registry/apis/provisioning/repository/github/mutator.go index 2c7c4a57827..4aec95aa7d6 100644 --- a/pkg/registry/apis/provisioning/repository/github/mutator.go +++ b/pkg/registry/apis/provisioning/repository/github/mutator.go @@ -2,6 +2,7 @@ package github import ( "context" + "strings" "k8s.io/apimachinery/pkg/runtime" @@ -21,6 +22,15 @@ func Mutator(secrets secrets.RepositorySecrets) controller.Mutator { return nil } + // Trim trailing ".git" and any trailing slash from the GitHub URL, if present, using the strings package. + if repo.Spec.GitHub.URL != "" { + url := repo.Spec.GitHub.URL + url = strings.TrimRight(url, "/") + url = strings.TrimSuffix(url, ".git") + url = strings.TrimRight(url, "/") + repo.Spec.GitHub.URL = url + } + if repo.Spec.GitHub.Token != "" { secretName := repo.Name + githubTokenSecretSuffix nameOrValue, err := secrets.Encrypt(ctx, repo, secretName, repo.Spec.GitHub.Token) diff --git a/pkg/registry/apis/provisioning/repository/github/mutator_test.go b/pkg/registry/apis/provisioning/repository/github/mutator_test.go index c60d53b5e88..a6bbbe22c11 100644 --- a/pkg/registry/apis/provisioning/repository/github/mutator_test.go +++ b/pkg/registry/apis/provisioning/repository/github/mutator_test.go @@ -22,6 +22,74 @@ func TestMutator(t *testing.T) { expectedEncryptedToken string expectedError string }{ + { + name: "trims trailing .git and slash from GitHub URL", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "repo1", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/org/repo.git/", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {}, + expectedToken: "", + expectedEncryptedToken: "", + }, + { + name: "trims only trailing slash from GitHub URL", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "repo2", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/org/repo/", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {}, + expectedToken: "", + expectedEncryptedToken: "", + }, + { + name: "trims only trailing .git from GitHub URL", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "repo3", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/org/repo.git", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {}, + expectedToken: "", + expectedEncryptedToken: "", + }, + { + name: "does not trim if no .git or slash", + obj: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "repo4", + Namespace: "default", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/org/repo", + }, + }, + }, + setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {}, + expectedToken: "", + expectedEncryptedToken: "", + }, { name: "successful token encryption", obj: &provisioning.Repository{ diff --git a/pkg/registry/apis/provisioning/repository/github/repository.go b/pkg/registry/apis/provisioning/repository/github/repository.go index e7aed375242..d2da1de41d1 100644 --- a/pkg/registry/apis/provisioning/repository/github/repository.go +++ b/pkg/registry/apis/provisioning/repository/github/repository.go @@ -22,7 +22,7 @@ const githubTokenSecretSuffix = "-github-token" // Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included. type githubRepository struct { - gitRepo git.GitRepository + git.GitRepository config *provisioning.Repository gh Client // assumes github.com base URL secrets secrets.RepositorySecrets @@ -62,19 +62,15 @@ func NewGitHub( } return &githubRepository{ - config: config, - gitRepo: gitRepo, - gh: factory.New(ctx, token), // TODO, baseURL from config - owner: owner, - repo: repo, - secrets: secrets, + config: config, + GitRepository: gitRepo, + gh: factory.New(ctx, token), // TODO, baseURL from config + owner: owner, + repo: repo, + secrets: secrets, }, nil } -func (r *githubRepository) Config() *provisioning.Repository { - return r.gitRepo.Config() -} - func (r *githubRepository) Owner() string { return r.owner } @@ -89,7 +85,7 @@ func (r *githubRepository) Client() Client { // Validate implements provisioning.Repository. func (r *githubRepository) Validate() (list field.ErrorList) { - cfg := r.gitRepo.Config() + cfg := r.Config() gh := cfg.Spec.GitHub if gh == nil { list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required")) @@ -110,11 +106,14 @@ func (r *githubRepository) Validate() (list field.ErrorList) { return list } - return r.gitRepo.Validate() + return r.GitRepository.Validate() } func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) { - parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git")) + giturl = strings.TrimSuffix(giturl, ".git") + giturl = strings.TrimSuffix(giturl, "/") + + parsed, e := url.Parse(giturl) if e != nil { err = e return @@ -136,32 +135,7 @@ func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, field.NewPath("spec", "github", "url"), url, err.Error())), nil } - return r.gitRepo.Test(ctx) -} - -// ReadResource implements provisioning.Repository. -func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) { - return r.gitRepo.Read(ctx, filePath, ref) -} - -func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) { - return r.gitRepo.ReadTree(ctx, ref) -} - -func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error { - return r.gitRepo.Create(ctx, path, ref, data, comment) -} - -func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error { - return r.gitRepo.Update(ctx, path, ref, data, comment) -} - -func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error { - return r.gitRepo.Write(ctx, path, ref, data, message) -} - -func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error { - return r.gitRepo.Delete(ctx, path, ref, comment) + return r.GitRepository.Test(ctx) } func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) { @@ -211,7 +185,7 @@ func (r *githubRepository) History(ctx context.Context, path, ref string) ([]pro // ListRefs list refs from the git repository and add the ref URL to the ref item func (r *githubRepository) ListRefs(ctx context.Context) ([]provisioning.RefItem, error) { - refs, err := r.gitRepo.ListRefs(ctx) + refs, err := r.GitRepository.ListRefs(ctx) if err != nil { return nil, fmt.Errorf("list refs: %w", err) } @@ -223,14 +197,6 @@ func (r *githubRepository) ListRefs(ctx context.Context) ([]provisioning.RefItem return refs, nil } -func (r *githubRepository) LatestRef(ctx context.Context) (string, error) { - return r.gitRepo.LatestRef(ctx) -} - -func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) { - return r.gitRepo.CompareFiles(ctx, base, ref) -} - // ResourceURLs implements RepositoryWithURLs. func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) { cfg := r.config.Spec.GitHub @@ -258,10 +224,6 @@ func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.Fi return urls, nil } -func (r *githubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) { - return r.gitRepo.Stage(ctx, opts) -} - func (r *githubRepository) OnCreate(_ context.Context) ([]map[string]interface{}, error) { return nil, nil } diff --git a/pkg/registry/apis/provisioning/repository/github/repository_test.go b/pkg/registry/apis/provisioning/repository/github/repository_test.go index 2301156529e..962a7a4e2b1 100644 --- a/pkg/registry/apis/provisioning/repository/github/repository_test.go +++ b/pkg/registry/apis/provisioning/repository/github/repository_test.go @@ -105,7 +105,7 @@ func TestNewGitHub(t *testing.T) { assert.Equal(t, tt.expectedRepo, repo.Repo()) concreteRepo, ok := repo.(*githubRepository) require.True(t, ok) - assert.Equal(t, gitRepo, concreteRepo.gitRepo) + assert.Equal(t, gitRepo, concreteRepo.GitRepository) } }) } @@ -301,8 +301,8 @@ func TestGitHubRepositoryValidate(t *testing.T) { } repo := &githubRepository{ - config: tt.config, - gitRepo: mockGitRepo, + config: tt.config, + GitRepository: mockGitRepo, } errors := repo.Validate() @@ -388,10 +388,10 @@ func TestGitHubRepositoryTest(t *testing.T) { } repo := &githubRepository{ - config: tt.config, - gitRepo: mockGitRepo, - owner: "grafana", - repo: "grafana", + config: tt.config, + GitRepository: mockGitRepo, + owner: "grafana", + repo: "grafana", } result, err := repo.Test(context.Background()) @@ -802,8 +802,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Config").Return(config) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result := repo.Config() @@ -822,8 +822,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Read", ctx, "test.yaml", "main").Return(expectedFileInfo, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.Read(ctx, "test.yaml", "main") @@ -840,8 +840,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("ReadTree", ctx, "main").Return(expectedEntries, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.ReadTree(ctx, "main") @@ -856,8 +856,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Create", ctx, "new-file.yaml", "main", data, "Create new file").Return(nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } err := repo.Create(ctx, "new-file.yaml", "main", data, "Create new file") @@ -871,8 +871,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Update", ctx, "existing-file.yaml", "main", data, "Update file").Return(nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } err := repo.Update(ctx, "existing-file.yaml", "main", data, "Update file") @@ -886,8 +886,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Write", ctx, "file.yaml", "main", data, "Write file").Return(nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } err := repo.Write(ctx, "file.yaml", "main", data, "Write file") @@ -900,8 +900,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("Delete", ctx, "file.yaml", "main", "Delete file").Return(nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } err := repo.Delete(ctx, "file.yaml", "main", "Delete file") @@ -915,8 +915,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("LatestRef", ctx).Return(expectedRef, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.LatestRef(ctx) @@ -935,8 +935,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("ListRefs", ctx).Return(gitRepoRefs, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.ListRefs(ctx) @@ -971,8 +971,8 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo.On("CompareFiles", ctx, "main", "feature-branch").Return(expectedChanges, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.CompareFiles(ctx, "main", "feature-branch") @@ -985,14 +985,14 @@ func TestGitHubRepositoryDelegation(t *testing.T) { mockGitRepo := git.NewMockGitRepository(t) mockStagedRepo := repository.NewMockStagedRepository(t) opts := repository.StageOptions{ - PushOnWrites: true, - Timeout: 10 * time.Second, + Mode: repository.StageModeCommitOnEach, + Timeout: 10 * time.Second, } mockGitRepo.On("Stage", ctx, opts).Return(mockStagedRepo, nil) repo := &githubRepository{ - config: config, - gitRepo: mockGitRepo, + config: config, + GitRepository: mockGitRepo, } result, err := repo.Stage(ctx, opts) @@ -1126,3 +1126,92 @@ func TestGitHubRepository_OnDelete(t *testing.T) { }) } } + +func TestGithubRepository_Move(t *testing.T) { + tests := []struct { + name string + oldPath string + newPath string + ref string + comment string + setupMock func(*git.MockGitRepository) + expectedErr error + }{ + { + name: "successful move delegates to git repository", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + setupMock: func(mockGitRepo *git.MockGitRepository) { + mockGitRepo.EXPECT().Move(context.Background(), "old.yaml", "new.yaml", "main", "move file").Return(nil) + }, + expectedErr: nil, + }, + { + name: "move error from git repository", + oldPath: "old.yaml", + newPath: "new.yaml", + ref: "main", + comment: "move file", + setupMock: func(mockGitRepo *git.MockGitRepository) { + mockGitRepo.EXPECT().Move(context.Background(), "old.yaml", "new.yaml", "main", "move file").Return(errors.New("git move failed")) + }, + expectedErr: errors.New("git move failed"), + }, + { + name: "successful directory move", + oldPath: "old/", + newPath: "new/", + ref: "main", + comment: "move directory", + setupMock: func(mockGitRepo *git.MockGitRepository) { + mockGitRepo.EXPECT().Move(context.Background(), "old/", "new/", "main", "move directory").Return(nil) + }, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock git repository + mockGitRepo := git.NewMockGitRepository(t) + mockSecrets := &secrets.MockRepositorySecrets{} + + // Setup mock expectations + tt.setupMock(mockGitRepo) + + // Create GitHub repository + config := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + }, + Spec: provisioning.RepositorySpec{ + Type: provisioning.GitHubRepositoryType, + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/example/repo", + }, + }, + } + + githubRepo := &githubRepository{ + config: config, + GitRepository: mockGitRepo, + owner: "example", + repo: "repo", + secrets: mockSecrets, + } + + // Execute move operation + err := githubRepo.Move(context.Background(), tt.oldPath, tt.newPath, tt.ref, tt.comment) + + // Verify results + if tt.expectedErr != nil { + require.Error(t, err) + assert.Equal(t, tt.expectedErr.Error(), err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/registry/apis/provisioning/repository/local/local.go b/pkg/registry/apis/provisioning/repository/local/local.go index 5f63b83cfa1..0ab620e6639 100644 --- a/pkg/registry/apis/provisioning/repository/local/local.go +++ b/pkg/registry/apis/provisioning/repository/local/local.go @@ -372,3 +372,63 @@ func (r *localRepository) Delete(ctx context.Context, path string, ref string, c return os.Remove(fullPath) } + +func (r *localRepository) Move(ctx context.Context, oldPath, newPath, ref, comment string) error { + if err := r.validateRequest(ref); err != nil { + return err + } + + oldFullPath := safepath.Join(r.path, oldPath) + newFullPath := safepath.Join(r.path, newPath) + + // Check if source exists + sourceInfo, err := os.Stat(oldFullPath) + if errors.Is(err, os.ErrNotExist) { + return repository.ErrFileNotFound + } else if err != nil { + return fmt.Errorf("check source: %w", err) + } + + // Check if destination already exists + if _, err := os.Stat(newFullPath); !errors.Is(err, os.ErrNotExist) { + if err != nil { + return fmt.Errorf("check destination: %w", err) + } + return repository.ErrFileAlreadyExists + } + + // Validate move types + sourceIsDir := sourceInfo.IsDir() + targetIsDir := safepath.IsDir(newPath) + + if sourceIsDir != targetIsDir { + return apierrors.NewBadRequest("cannot move between file and directory types") + } + + // Create destination directory if needed + if !sourceIsDir { + // For file moves, create the directory containing the file + destParent := path.Dir(newFullPath) + if err := os.MkdirAll(destParent, 0700); err != nil { + return fmt.Errorf("create destination directory: %w", err) + } + } else { + // For directory moves, create the parent directory of the destination + // but not the destination directory itself (os.Rename will create it) + // We need to be careful with trailing slashes in directory paths + cleanNewPath := strings.TrimSuffix(newFullPath, "/") + destParent := path.Dir(cleanNewPath) + if destParent != "." && destParent != "/" && destParent != r.path { + if err := os.MkdirAll(destParent, 0700); err != nil { + return fmt.Errorf("create destination parent directory: %w", err) + } + } + } + + // Move the file or directory + if err := os.Rename(oldFullPath, newFullPath); err != nil { + return fmt.Errorf("move: %w", err) + } + + return nil +} diff --git a/pkg/registry/apis/provisioning/repository/local/local_test.go b/pkg/registry/apis/provisioning/repository/local/local_test.go index 7895da24995..77a0b99fa09 100644 --- a/pkg/registry/apis/provisioning/repository/local/local_test.go +++ b/pkg/registry/apis/provisioning/repository/local/local_test.go @@ -1517,3 +1517,287 @@ func TestLocalRepository_Config(t *testing.T) { }) } } + +func TestLocalRepository_Move(t *testing.T) { + testCases := []struct { + name string + setup func(t *testing.T) (string, *localRepository) + oldPath string + newPath string + ref string + comment string + expectedErr error + expectedContent string // Expected content of moved file (empty string means don't verify content) + }{ + { + name: "successful move", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create source file + sourceFile := filepath.Join(tempDir, "source.txt") + err := os.WriteFile(sourceFile, []byte("source content"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "source.txt", + newPath: "destination.txt", + ref: "", + comment: "move file", + expectedErr: nil, + expectedContent: "source content", + }, + { + name: "move to subdirectory", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create source file + sourceFile := filepath.Join(tempDir, "test.txt") + err := os.WriteFile(sourceFile, []byte("test content"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "test.txt", + newPath: "newdir/moved.txt", + ref: "", + comment: "move to subdir", + expectedErr: nil, + expectedContent: "test content", + }, + { + name: "move non-existent file", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "nonexistent.txt", + newPath: "destination.txt", + ref: "", + comment: "move missing", + expectedErr: repository.ErrFileNotFound, + expectedContent: "", // No content verification for error cases + }, + { + name: "move to existing file", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create source and destination files + sourceFile := filepath.Join(tempDir, "source2.txt") + destFile := filepath.Join(tempDir, "dest2.txt") + + err := os.WriteFile(sourceFile, []byte("source"), 0600) + require.NoError(t, err) + err = os.WriteFile(destFile, []byte("destination"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "source2.txt", + newPath: "dest2.txt", + ref: "", + comment: "move to existing", + expectedErr: repository.ErrFileAlreadyExists, + expectedContent: "", // No content verification for error cases + }, + { + name: "successful directory move", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create directory with files + subdir := filepath.Join(tempDir, "subdir") + err := os.MkdirAll(subdir, 0700) + require.NoError(t, err) + + // Add a file inside the directory + err = os.WriteFile(filepath.Join(subdir, "file.txt"), []byte("dir content"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "subdir/", + newPath: "newsubdir/", + ref: "", + comment: "move directory", + expectedErr: nil, + expectedContent: "", // No content verification for directory moves + }, + { + name: "move file to directory type should fail", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create source file + sourceFile := filepath.Join(tempDir, "file.txt") + err := os.WriteFile(sourceFile, []byte("content"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "file.txt", + newPath: "directory/", + ref: "", + comment: "move file to directory", + expectedErr: apierrors.NewBadRequest("cannot move between file and directory types"), + expectedContent: "", // No content verification for error cases + }, + { + name: "move with ref should fail", + setup: func(t *testing.T) (string, *localRepository) { + tempDir := t.TempDir() + + // Create test file + testFile := filepath.Join(tempDir, "ref_test.txt") + err := os.WriteFile(testFile, []byte("ref test content"), 0600) + require.NoError(t, err) + + repo := &localRepository{ + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Local: &provisioning.LocalRepositoryConfig{ + Path: tempDir, + }, + }, + }, + resolver: &LocalFolderResolver{ + PermittedPrefixes: []string{tempDir}, + }, + path: tempDir, + } + + return tempDir, repo + }, + oldPath: "ref_test.txt", + newPath: "ref_dest.txt", + ref: "some-ref", + comment: "move with ref", + expectedErr: apierrors.NewBadRequest("local repository does not support ref"), + expectedContent: "", // No content verification for error cases + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup test environment + tempDir, repo := tc.setup(t) + + // Execute the move operation + err := repo.Move(context.Background(), tc.oldPath, tc.newPath, tc.ref, tc.comment) + + // Verify results + if tc.expectedErr != nil { + require.Error(t, err) + assert.Equal(t, tc.expectedErr.Error(), err.Error(), "Error message should match expected") + } else { + require.NoError(t, err) + + // Verify source file no longer exists + sourceFullPath := filepath.Join(tempDir, tc.oldPath) + _, err = os.Stat(sourceFullPath) + assert.True(t, errors.Is(err, os.ErrNotExist), "Source file should no longer exist") + + // Verify destination file exists + destFullPath := filepath.Join(tempDir, tc.newPath) + _, err = os.Stat(destFullPath) + require.NoError(t, err, "Destination file should exist") + + // Verify content if expectedContent is specified + if tc.expectedContent != "" { + //nolint:gosec // G304: is only for tests + content, err := os.ReadFile(destFullPath) + require.NoError(t, err) + assert.Equal(t, tc.expectedContent, string(content), "Content should be preserved") + } + } + }) + } +} diff --git a/pkg/registry/apis/provisioning/repository/mock_wrap_with_stage_fn.go b/pkg/registry/apis/provisioning/repository/mock_wrap_with_stage_fn.go new file mode 100644 index 00000000000..e4c6108e83c --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/mock_wrap_with_stage_fn.go @@ -0,0 +1,85 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package repository + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockWrapWithStageFn is an autogenerated mock type for the WrapWithStageFn type +type MockWrapWithStageFn struct { + mock.Mock +} + +type MockWrapWithStageFn_Expecter struct { + mock *mock.Mock +} + +func (_m *MockWrapWithStageFn) EXPECT() *MockWrapWithStageFn_Expecter { + return &MockWrapWithStageFn_Expecter{mock: &_m.Mock} +} + +// Execute provides a mock function with given fields: ctx, repo, stageOptions, fn +func (_m *MockWrapWithStageFn) Execute(ctx context.Context, repo Repository, stageOptions StageOptions, fn func(Repository, bool) error) error { + ret := _m.Called(ctx, repo, stageOptions, fn) + + if len(ret) == 0 { + panic("no return value specified for Execute") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, Repository, StageOptions, func(Repository, bool) error) error); ok { + r0 = rf(ctx, repo, stageOptions, fn) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockWrapWithStageFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute' +type MockWrapWithStageFn_Execute_Call struct { + *mock.Call +} + +// Execute is a helper method to define mock.On call +// - ctx context.Context +// - repo Repository +// - stageOptions StageOptions +// - fn func(Repository , bool) error +func (_e *MockWrapWithStageFn_Expecter) Execute(ctx interface{}, repo interface{}, stageOptions interface{}, fn interface{}) *MockWrapWithStageFn_Execute_Call { + return &MockWrapWithStageFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repo, stageOptions, fn)} +} + +func (_c *MockWrapWithStageFn_Execute_Call) Run(run func(ctx context.Context, repo Repository, stageOptions StageOptions, fn func(Repository, bool) error)) *MockWrapWithStageFn_Execute_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(Repository), args[2].(StageOptions), args[3].(func(Repository, bool) error)) + }) + return _c +} + +func (_c *MockWrapWithStageFn_Execute_Call) Return(_a0 error) *MockWrapWithStageFn_Execute_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockWrapWithStageFn_Execute_Call) RunAndReturn(run func(context.Context, Repository, StageOptions, func(Repository, bool) error) error) *MockWrapWithStageFn_Execute_Call { + _c.Call.Return(run) + return _c +} + +// NewMockWrapWithStageFn creates a new instance of MockWrapWithStageFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockWrapWithStageFn(t interface { + mock.TestingT + Cleanup(func()) +}) *MockWrapWithStageFn { + mock := &MockWrapWithStageFn{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/repository.go b/pkg/registry/apis/provisioning/repository/repository.go index 0e72dabbf43..718660c9fab 100644 --- a/pkg/registry/apis/provisioning/repository/repository.go +++ b/pkg/registry/apis/provisioning/repository/repository.go @@ -114,6 +114,9 @@ type Writer interface { // Delete a file in the remote repository Delete(ctx context.Context, path, ref, message string) error + + // Move a file from one path to another in the remote repository + Move(ctx context.Context, oldPath, newPath, ref, message string) error } type ReaderWriter interface { diff --git a/pkg/registry/apis/provisioning/repository/repository_mock.go b/pkg/registry/apis/provisioning/repository/repository_mock.go index b7f5bda049b..05fc5a3832a 100644 --- a/pkg/registry/apis/provisioning/repository/repository_mock.go +++ b/pkg/registry/apis/provisioning/repository/repository_mock.go @@ -280,6 +280,24 @@ func (_m *MockRepository) Write(ctx context.Context, path string, ref string, da return r0 } +// Move provides a mock function with given fields: ctx, oldPath, newPath, ref, message +func (_m *MockRepository) Move(ctx context.Context, oldPath string, newPath string, ref string, message string) error { + ret := _m.Called(ctx, oldPath, newPath, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Move") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, oldPath, newPath, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockRepository(t interface { diff --git a/pkg/registry/apis/provisioning/repository/staged.go b/pkg/registry/apis/provisioning/repository/staged.go index 6eb0aed357a..ca5b59c72c9 100644 --- a/pkg/registry/apis/provisioning/repository/staged.go +++ b/pkg/registry/apis/provisioning/repository/staged.go @@ -10,11 +10,32 @@ import ( "github.com/grafana/nanogit" ) +//go:generate mockery --name WrapWithStageFn --structname MockWrapWithStageFn --inpackage --filename mock_wrap_with_stage_fn.go --with-expecter +type WrapWithStageFn func(ctx context.Context, repo Repository, stageOptions StageOptions, fn func(repo Repository, staged bool) error) error + +// StageMode defines the staging and commit behavior +type StageMode int + +const ( + // StageModeCommitOnEach commits each file operation individually (default) + StageModeCommitOnEach StageMode = iota + // StageModeCommitOnlyOnce stages all changes and commits them all at once on push + StageModeCommitOnlyOnce + // StageModeCommitAndPushOnEach commits and pushes each file operation individually + StageModeCommitAndPushOnEach +) + type StageOptions struct { + // Ref custom ref + Ref string // Push on every write PushOnWrites bool + // Mode defines the staging and commit behavior + Mode StageMode // Maximum time allowed for clone operation in seconds (0 means no limit) Timeout time.Duration + // Commit message to use when Mode is StageModeCommitOnlyOnce + CommitOnlyOnceMessage string } //go:generate mockery --name StageableRepository --structname MockStageableRepository --inpackage --filename stageable_repository_mock.go --with-expecter diff --git a/pkg/registry/apis/provisioning/repository/staged_repository_mock.go b/pkg/registry/apis/provisioning/repository/staged_repository_mock.go index 916b6cc678e..85bfb4f3012 100644 --- a/pkg/registry/apis/provisioning/repository/staged_repository_mock.go +++ b/pkg/registry/apis/provisioning/repository/staged_repository_mock.go @@ -170,6 +170,56 @@ func (_c *MockStagedRepository_Delete_Call) RunAndReturn(run func(context.Contex return _c } +// Move provides a mock function with given fields: ctx, oldPath, newPath, ref, message +func (_m *MockStagedRepository) Move(ctx context.Context, oldPath string, newPath string, ref string, message string) error { + ret := _m.Called(ctx, oldPath, newPath, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Move") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, oldPath, newPath, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockStagedRepository_Move_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Move' +type MockStagedRepository_Move_Call struct { + *mock.Call +} + +// Move is a helper method to define mock.On call +// - ctx context.Context +// - oldPath string +// - newPath string +// - ref string +// - message string +func (_e *MockStagedRepository_Expecter) Move(ctx interface{}, oldPath interface{}, newPath interface{}, ref interface{}, message interface{}) *MockStagedRepository_Move_Call { + return &MockStagedRepository_Move_Call{Call: _e.mock.On("Move", ctx, oldPath, newPath, ref, message)} +} + +func (_c *MockStagedRepository_Move_Call) Run(run func(ctx context.Context, oldPath string, newPath string, ref string, message string)) *MockStagedRepository_Move_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MockStagedRepository_Move_Call) Return(_a0 error) *MockStagedRepository_Move_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockStagedRepository_Move_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockStagedRepository_Move_Call { + _c.Call.Return(run) + return _c +} + // Push provides a mock function with given fields: ctx func (_m *MockStagedRepository) Push(ctx context.Context) error { ret := _m.Called(ctx) diff --git a/pkg/registry/apis/provisioning/resources/client.go b/pkg/registry/apis/provisioning/resources/client.go index b27f45b6892..afc5261b2e8 100644 --- a/pkg/registry/apis/provisioning/resources/client.go +++ b/pkg/registry/apis/provisioning/resources/client.go @@ -11,7 +11,8 @@ import ( "k8s.io/client-go/dynamic" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" iam "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver" @@ -19,10 +20,11 @@ import ( ) var ( - UserResource = iam.UserResourceInfo.GroupVersionResource() - FolderResource = folders.FolderResourceInfo.GroupVersionResource() - DashboardResource = dashboardV1.DashboardResourceInfo.GroupVersionResource() - DashboardResourceV2 = dashboardV2.DashboardResourceInfo.GroupVersionResource() + UserResource = iam.UserResourceInfo.GroupVersionResource() + FolderResource = folders.FolderResourceInfo.GroupVersionResource() + DashboardResource = dashboardV1.DashboardResourceInfo.GroupVersionResource() + DashboardResourceV2alpha1 = dashboardV2alpha1.DashboardResourceInfo.GroupVersionResource() + DashboardResourceV2alpha2 = dashboardV2alpha2.DashboardResourceInfo.GroupVersionResource() // SupportedProvisioningResources is the list of resources that can fully managed from the UI SupportedProvisioningResources = []schema.GroupVersionResource{FolderResource, DashboardResource} diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go index 2145285d52e..50efaa13048 100644 --- a/pkg/registry/apis/provisioning/resources/dualwriter.go +++ b/pkg/registry/apis/provisioning/resources/dualwriter.go @@ -28,11 +28,12 @@ type DualReadWriter struct { } type DualWriteOptions struct { - Path string - Ref string - Message string - Data []byte - SkipDryRun bool + Path string + Ref string + Message string + Data []byte + SkipDryRun bool + OriginalPath string // Used for move operations } func NewDualReadWriter(repo repository.ReaderWriter, parser Parser, folders *FolderManager, access authlib.AccessChecker) *DualReadWriter { @@ -278,6 +279,216 @@ func (r *DualReadWriter) createOrUpdate(ctx context.Context, create bool, opts D return parsed, err } +// MoveResource moves a resource from one path to another in the repository +func (r *DualReadWriter) MoveResource(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) { + if err := repository.IsWriteAllowed(r.repo.Config(), opts.Ref); err != nil { + return nil, err + } + + if opts.OriginalPath == "" { + return nil, fmt.Errorf("originalPath is required for move operations") + } + + // Validate that both paths are either files or directories (consistent types) + // Files should end without '/', directories should end with '/' + sourceIsDir := safepath.IsDir(opts.OriginalPath) + targetIsDir := safepath.IsDir(opts.Path) + if sourceIsDir != targetIsDir { + return nil, fmt.Errorf("cannot move between file and directory types - source is %s, target is %s", + getPathType(sourceIsDir), getPathType(targetIsDir)) + } + + // Handle directory moves separately (no parsing/authorization needed) + if sourceIsDir { + return r.moveDirectory(ctx, opts) + } + + // Handle file moves with parsing and authorization + return r.moveFile(ctx, opts) +} + +func (r *DualReadWriter) moveDirectory(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) { + // For directory moves, we just perform the repository move without parsing + // Always use the provisioning identity when writing + ctx, _, err := identity.WithProvisioningIdentity(ctx, r.repo.Config().Namespace) + if err != nil { + return nil, fmt.Errorf("unable to use provisioning identity: %w", err) + } + + // Perform the move operation in the repository + if err = r.repo.Move(ctx, opts.OriginalPath, opts.Path, opts.Ref, opts.Message); err != nil { + return nil, fmt.Errorf("move directory in repository: %w", err) + } + + // Create a basic parsed resource response for directories + cfg := r.repo.Config() + parsed := &ParsedResource{ + Action: provisioning.ResourceActionMove, + Info: &repository.FileInfo{ + Path: opts.Path, + Ref: opts.Ref, + }, + GVK: schema.GroupVersionKind{ + Group: FolderResource.Group, + Version: FolderResource.Version, + Kind: "Folder", + }, + GVR: FolderResource, + Repo: provisioning.ResourceRepositoryInfo{ + Type: cfg.Spec.Type, + Namespace: cfg.Namespace, + Name: cfg.Name, + Title: cfg.Spec.Title, + }, + } + + // Handle folder management for main branch + if opts.Ref == "" { + // Ensure destination folder path exists + if _, err := r.folders.EnsureFolderPathExist(ctx, opts.Path); err != nil { + return nil, fmt.Errorf("ensure destination folder path exists: %w", err) + } + + // Try to delete the old folder structure from grafana (if it exists) + // This handles cleanup when folders are moved to new locations + oldFolderName, err := r.folders.EnsureFolderPathExist(ctx, opts.OriginalPath) + if err != nil { + return nil, fmt.Errorf("ensure original folder path exists: %w", err) + } + + if oldFolderName != "" { + oldFolder, err := r.folders.GetFolder(ctx, oldFolderName) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("get old folder for cleanup: %w", err) + } + + if err == nil { + err = r.folders.Client().Delete(ctx, oldFolder.GetName(), metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("delete old folder from storage: %w", err) + } + } + } + } + + return parsed, nil +} + +func (r *DualReadWriter) moveFile(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) { + // Read the original file to get its content for parsing and authorization + originalFile, err := r.repo.Read(ctx, opts.OriginalPath, "") + if err != nil { + return nil, fmt.Errorf("read original file: %w", err) + } + + // Parse the original file to check permissions + parsed, err := r.parser.Parse(ctx, originalFile) + if err != nil { + return nil, fmt.Errorf("parse original file: %w", err) + } + + // Authorize delete on the original path + if err = r.authorize(ctx, parsed, utils.VerbDelete); err != nil { + return nil, fmt.Errorf("not authorized to delete original file: %w", err) + } + + // Determine the content to use for the destination + // If new content is provided in opts.Data, use it; otherwise use original content + var destinationData []byte + if len(opts.Data) > 0 { + destinationData = opts.Data + } else { + destinationData = originalFile.Data + } + + // Create new parsed resource with updated path and content + newInfo := &repository.FileInfo{ + Data: destinationData, + Path: opts.Path, + Ref: opts.Ref, + } + + newParsed, err := r.parser.Parse(ctx, newInfo) + if err != nil { + return nil, fmt.Errorf("parse new file: %w", err) + } + + // Make sure the new resource is valid + if !opts.SkipDryRun { + if err := newParsed.DryRun(ctx); err != nil { + logger := logging.FromContext(ctx).With("path", opts.Path, "originalPath", opts.OriginalPath, "name", newParsed.Obj.GetName(), "ref", opts.Ref) + logger.Warn("failed to dry run resource on move", "error", err) + return nil, fmt.Errorf("error running dryRun on moved resource: %w", err) + } + } + + if len(newParsed.Errors) > 0 { + return nil, fmt.Errorf("errors while parsing moved file [%v]", newParsed.Errors) + } + + // Authorize create on the new path + verb := utils.VerbCreate + if newParsed.Action == provisioning.ResourceActionUpdate { + verb = utils.VerbUpdate + } + if err = r.authorize(ctx, newParsed, verb); err != nil { + return nil, fmt.Errorf("not authorized to create new file: %w", err) + } + + data, err := newParsed.ToSaveBytes() + if err != nil { + return nil, err + } + + // Always use the provisioning identity when writing + ctx, _, err = identity.WithProvisioningIdentity(ctx, newParsed.Obj.GetNamespace()) + if err != nil { + return nil, fmt.Errorf("unable to use provisioning identity: %w", err) + } + + // Perform the move operation in the repository + // If we have new content, we need to update the file content as part of the move + if len(opts.Data) > 0 { + // For moves with content updates, we need to delete the old file and create the new one + if err = r.repo.Delete(ctx, opts.OriginalPath, opts.Ref, opts.Message); err != nil { + return nil, fmt.Errorf("delete original file in repository: %w", err) + } + if err = r.repo.Create(ctx, opts.Path, opts.Ref, data, opts.Message); err != nil { + return nil, fmt.Errorf("create moved file with new content in repository: %w", err) + } + } else { + // For simple moves without content changes, use the move operation + if err = r.repo.Move(ctx, opts.OriginalPath, opts.Path, opts.Ref, opts.Message); err != nil { + return nil, fmt.Errorf("move file in repository: %w", err) + } + } + + // Update the grafana database if this is the main branch + if opts.Ref == "" && newParsed.Client != nil { + if _, err := r.folders.EnsureFolderPathExist(ctx, opts.Path); err != nil { + return nil, fmt.Errorf("ensure folder path exists: %w", err) + } + + // Delete the old resource from grafana if name changed + if newParsed.Obj.GetName() != parsed.Obj.GetName() { + err = parsed.Client.Delete(ctx, parsed.Obj.GetName(), metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("delete original resource from storage: %w", err) + } + } + + // Create/update the new resource in grafana + err = newParsed.Run(ctx) + if err != nil { + return nil, fmt.Errorf("create moved resource in storage: %w", err) + } + } + + newParsed.Action = provisioning.ResourceActionMove + + return newParsed, nil +} + func (r *DualReadWriter) authorize(ctx context.Context, parsed *ParsedResource, verb string) error { id, err := identity.GetRequester(ctx) if err != nil { @@ -376,6 +587,14 @@ func getFolderURLs(ctx context.Context, path, ref string, repo repository.Reposi return nil, nil } +// getPathType returns a human-readable description of the path type +func getPathType(isDir bool) string { + if isDir { + return "directory (ends with '/')" + } + return "file (no trailing '/')" +} + func folderDeleteResponse(ctx context.Context, path, ref string, repo repository.Repository) (*ParsedResource, error) { urls, err := getFolderURLs(ctx, path, ref, repo) if err != nil { diff --git a/pkg/registry/apis/provisioning/resources/dualwriter_test.go b/pkg/registry/apis/provisioning/resources/dualwriter_test.go new file mode 100644 index 00000000000..10ec8270655 --- /dev/null +++ b/pkg/registry/apis/provisioning/resources/dualwriter_test.go @@ -0,0 +1,147 @@ +package resources + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath" +) + +func TestGetPathType(t *testing.T) { + tests := []struct { + name string + isDir bool + expected string + }{ + { + name: "directory path", + isDir: true, + expected: "directory (ends with '/')", + }, + { + name: "file path", + isDir: false, + expected: "file (no trailing '/')", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getPathType(tt.isDir) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestMovePathValidation(t *testing.T) { + tests := []struct { + name string + originalPath string + newPath string + expectError bool + errorMessage string + }{ + { + name: "file to file move (valid)", + originalPath: "old/file.json", + newPath: "new/file.json", + expectError: false, + }, + { + name: "directory to directory move (valid)", + originalPath: "old/folder/", + newPath: "new/folder/", + expectError: false, + }, + { + name: "file to directory move (invalid)", + originalPath: "old/file.json", + newPath: "new/folder/", + expectError: true, + errorMessage: "cannot move between file and directory types", + }, + { + name: "directory to file move (invalid)", + originalPath: "old/folder/", + newPath: "new/file.json", + expectError: true, + errorMessage: "cannot move between file and directory types", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test the path validation logic that would be used in MoveResource + sourceIsDir := safepath.IsDir(tt.originalPath) + targetIsDir := safepath.IsDir(tt.newPath) + + if tt.expectError { + assert.NotEqual(t, sourceIsDir, targetIsDir, "Path types should be different for invalid moves") + } else { + assert.Equal(t, sourceIsDir, targetIsDir, "Path types should be the same for valid moves") + } + }) + } +} + +func TestMoveOptionsContentHandling(t *testing.T) { + tests := []struct { + name string + opts DualWriteOptions + originalData []byte + expectedContentToUse []byte + expectedUseOriginal bool + }{ + { + name: "move with new content provided", + opts: DualWriteOptions{ + Path: "new/file.json", + OriginalPath: "old/file.json", + Data: []byte(`{"updated": "content"}`), + }, + originalData: []byte(`{"original": "content"}`), + expectedContentToUse: []byte(`{"updated": "content"}`), + expectedUseOriginal: false, + }, + { + name: "move without new content (nil)", + opts: DualWriteOptions{ + Path: "new/file.json", + OriginalPath: "old/file.json", + Data: nil, + }, + originalData: []byte(`{"original": "content"}`), + expectedContentToUse: []byte(`{"original": "content"}`), + expectedUseOriginal: true, + }, + { + name: "move without new content (empty slice)", + opts: DualWriteOptions{ + Path: "new/file.json", + OriginalPath: "old/file.json", + Data: []byte{}, + }, + originalData: []byte(`{"original": "content"}`), + expectedContentToUse: []byte(`{"original": "content"}`), + expectedUseOriginal: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Simulate the content selection logic from moveFile method + var destinationData []byte + useOriginal := len(tt.opts.Data) == 0 + + if useOriginal { + destinationData = tt.originalData + } else { + destinationData = tt.opts.Data + } + + assert.Equal(t, tt.expectedUseOriginal, useOriginal, "Should correctly determine whether to use original content") + assert.Equal(t, tt.expectedContentToUse, destinationData, "Should select correct content for destination") + }) + } +} diff --git a/pkg/registry/apis/provisioning/resources/resources.go b/pkg/registry/apis/provisioning/resources/resources.go index b1f5cef53c2..28dd06ea43a 100644 --- a/pkg/registry/apis/provisioning/resources/resources.go +++ b/pkg/registry/apis/provisioning/resources/resources.go @@ -21,6 +21,7 @@ import ( var ( ErrAlreadyInRepository = errors.New("already in repository") + ErrDuplicateName = errors.New("duplicate name in repository") ErrMissingName = field.Required(field.NewPath("name", "metadata", "name"), "missing name in resource") ) @@ -152,7 +153,7 @@ func (r *ResourcesManager) WriteResourceFromFile(ctx context.Context, path strin } existing, found := r.resourcesLookup[id] if found { - return "", parsed.GVK, fmt.Errorf("duplicate resource name: %s, %s and %s", parsed.Obj.GetName(), path, existing) + return "", parsed.GVK, fmt.Errorf("duplicate resource name: %s, %s and %s: %w", parsed.Obj.GetName(), path, existing, ErrDuplicateName) } r.resourcesLookup[id] = path diff --git a/pkg/registry/apis/provisioning/secrets/mocks/decrypt_service_mock.go b/pkg/registry/apis/provisioning/secrets/mocks/decrypt_service_mock.go index 50ad9773e93..14e2d028689 100644 --- a/pkg/registry/apis/provisioning/secrets/mocks/decrypt_service_mock.go +++ b/pkg/registry/apis/provisioning/secrets/mocks/decrypt_service_mock.go @@ -5,7 +5,7 @@ package mocks import ( context "context" - service "github.com/grafana/grafana/pkg/registry/apis/secret/service" + contracts "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" mock "github.com/stretchr/testify/mock" ) @@ -23,7 +23,7 @@ func (_m *MockDecryptService) EXPECT() *MockDecryptService_Expecter { } // Decrypt provides a mock function with given fields: ctx, namespace, names -func (_m *MockDecryptService) Decrypt(ctx context.Context, namespace string, names ...string) (map[string]service.DecryptResult, error) { +func (_m *MockDecryptService) Decrypt(ctx context.Context, namespace string, names ...string) (map[string]contracts.DecryptResult, error) { _va := make([]interface{}, len(names)) for _i := range names { _va[_i] = names[_i] @@ -37,16 +37,16 @@ func (_m *MockDecryptService) Decrypt(ctx context.Context, namespace string, nam panic("no return value specified for Decrypt") } - var r0 map[string]service.DecryptResult + var r0 map[string]contracts.DecryptResult var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...string) (map[string]service.DecryptResult, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) (map[string]contracts.DecryptResult, error)); ok { return rf(ctx, namespace, names...) } - if rf, ok := ret.Get(0).(func(context.Context, string, ...string) map[string]service.DecryptResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) map[string]contracts.DecryptResult); ok { r0 = rf(ctx, namespace, names...) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]service.DecryptResult) + r0 = ret.Get(0).(map[string]contracts.DecryptResult) } } @@ -86,12 +86,12 @@ func (_c *MockDecryptService_Decrypt_Call) Run(run func(ctx context.Context, nam return _c } -func (_c *MockDecryptService_Decrypt_Call) Return(_a0 map[string]service.DecryptResult, _a1 error) *MockDecryptService_Decrypt_Call { +func (_c *MockDecryptService_Decrypt_Call) Return(_a0 map[string]contracts.DecryptResult, _a1 error) *MockDecryptService_Decrypt_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockDecryptService_Decrypt_Call) RunAndReturn(run func(context.Context, string, ...string) (map[string]service.DecryptResult, error)) *MockDecryptService_Decrypt_Call { +func (_c *MockDecryptService_Decrypt_Call) RunAndReturn(run func(context.Context, string, ...string) (map[string]contracts.DecryptResult, error)) *MockDecryptService_Decrypt_Call { _c.Call.Return(run) return _c } diff --git a/pkg/registry/apis/provisioning/secrets/repository.go b/pkg/registry/apis/provisioning/secrets/repository.go index 20d621b9def..c933d72bf92 100644 --- a/pkg/registry/apis/provisioning/secrets/repository.go +++ b/pkg/registry/apis/provisioning/secrets/repository.go @@ -7,17 +7,17 @@ import ( "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - grafanasecrets "github.com/grafana/grafana/pkg/services/secrets" + legacysecrets "github.com/grafana/grafana/pkg/services/secrets" ) func ProvideRepositorySecrets( features featuremgmt.FeatureToggles, - legacySecretsSvc grafanasecrets.Service, + legacySecretsSvc legacysecrets.Service, secretsSvc contracts.SecureValueClient, - decryptSvc service.DecryptService, + decryptSvc secret.DecryptService, ) RepositorySecrets { return NewRepositorySecrets(features, NewSecretsService(secretsSvc, decryptSvc), NewSingleTenant(legacySecretsSvc)) } @@ -56,7 +56,9 @@ func NewRepositorySecrets(features featuremgmt.FeatureToggles, secretsSvc Servic // If the feature flag is disabled, it uses the legacy secrets service // If the feature flag is enabled, it uses the secrets service func (s *repositorySecrets) Encrypt(ctx context.Context, r *provisioning.Repository, name string, data string) (nameOrValue []byte, err error) { + logger := logging.FromContext(ctx).With("name", name, "namespace", r.GetNamespace()) if s.features.IsEnabled(ctx, featuremgmt.FlagProvisioningSecretsService) { + logger.Info("Encrypting secret with new secrets service") encrypted, err := s.secretsSvc.Encrypt(ctx, r.GetNamespace(), name, data) if err != nil { return nil, err @@ -64,6 +66,7 @@ func (s *repositorySecrets) Encrypt(ctx context.Context, r *provisioning.Reposit return []byte(encrypted), err } + logger.Info("Encrypting secret with legacy secrets service") encrypted, err := s.legacySecrets.Encrypt(ctx, []byte(data)) if err != nil { return nil, err @@ -87,10 +90,10 @@ func (s *repositorySecrets) Decrypt(ctx context.Context, r *provisioning.Reposit logger := logging.FromContext(ctx) // HACK: this is a hack to identify if the name is a potential Kubernetes name for a secret. if strings.HasPrefix(nameOrValue, r.GetName()) { - logger.Info("Decrypting secret with new secrets service", "name", nameOrValue) + logger.Info("Decrypting secret with new secrets service") return s.secretsSvc.Decrypt(ctx, r.GetNamespace(), nameOrValue) } else { - logger.Info("Decrypting secret with legacy secrets service", "name", nameOrValue) + logger.Info("Decrypting secret with legacy secrets service") return s.legacySecrets.Decrypt(ctx, []byte(nameOrValue)) } } diff --git a/pkg/registry/apis/provisioning/secrets/secret.go b/pkg/registry/apis/provisioning/secrets/secret.go index 8353499adb6..de3e43deb63 100644 --- a/pkg/registry/apis/provisioning/secrets/secret.go +++ b/pkg/registry/apis/provisioning/secrets/secret.go @@ -6,20 +6,17 @@ import ( "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - grafanasecrets "github.com/grafana/grafana/pkg/registry/apis/secret/service" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" ) const svcName = "provisioning" //go:generate mockery --name SecureValueClient --structname MockSecureValueClient --inpackage --filename secure_value_client_mock.go --with-expecter -type SecureValueClient interface { - Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) -} +type SecureValueClient = secret.SecureValueClient //go:generate mockery --name Service --structname MockService --inpackage --filename secret_mock.go --with-expecter type Service interface { @@ -30,13 +27,13 @@ type Service interface { var _ Service = (*secretsService)(nil) -//go:generate mockery --name DecryptService --structname MockDecryptService --srcpkg=github.com/grafana/grafana/pkg/registry/apis/secret/service --filename decrypt_service_mock.go --with-expecter +//go:generate mockery --name DecryptService --structname MockDecryptService --srcpkg=github.com/grafana/grafana/pkg/registry/apis/secret --filename decrypt_service_mock.go --with-expecter type secretsService struct { secureValues SecureValueClient - decryptSvc grafanasecrets.DecryptService + decryptSvc secret.DecryptService } -func NewSecretsService(secretsSvc SecureValueClient, decryptSvc grafanasecrets.DecryptService) Service { +func NewSecretsService(secretsSvc SecureValueClient, decryptSvc secret.DecryptService) Service { return &secretsService{ secureValues: secretsSvc, decryptSvc: decryptSvc, diff --git a/pkg/registry/apis/provisioning/secrets/secret_test.go b/pkg/registry/apis/provisioning/secrets/secret_test.go index f7b12508b91..25b71bef1ad 100644 --- a/pkg/registry/apis/provisioning/secrets/secret_test.go +++ b/pkg/registry/apis/provisioning/secrets/secret_test.go @@ -7,8 +7,8 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets/mocks" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -258,7 +258,7 @@ func TestSecretsService_Decrypt(t *testing.T) { secretName: "test-secret", setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { exposedValue := secretv1beta1.NewExposedSecureValue("decrypted-data") - mockResult := service.NewDecryptResultValue(&exposedValue) + mockResult := secret.NewDecryptResultValue(&exposedValue) mockDecryptSvc.EXPECT().Decrypt( mock.MatchedBy(func(ctx context.Context) bool { @@ -267,7 +267,7 @@ func TestSecretsService_Decrypt(t *testing.T) { }), "test-namespace", "test-secret", - ).Return(map[string]service.DecryptResult{ + ).Return(map[string]secret.DecryptResult{ "test-secret": mockResult, }, nil) }, @@ -299,16 +299,16 @@ func TestSecretsService_Decrypt(t *testing.T) { }), "test-namespace", "test-secret", - ).Return(map[string]service.DecryptResult{}, nil) + ).Return(map[string]secret.DecryptResult{}, nil) }, - expectedError: contracts.ErrDecryptNotFound.Error(), + expectedError: secret.ErrDecryptNotFound.Error(), }, { name: "decrypt result has error", namespace: "test-namespace", secretName: "test-secret", setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { - mockResult := service.NewDecryptResultErr(errors.New("decryption failed")) + mockResult := secret.NewDecryptResultErr(errors.New("decryption failed")) mockDecryptSvc.EXPECT().Decrypt( mock.MatchedBy(func(ctx context.Context) bool { @@ -316,7 +316,7 @@ func TestSecretsService_Decrypt(t *testing.T) { }), "test-namespace", "test-secret", - ).Return(map[string]service.DecryptResult{ + ).Return(map[string]secret.DecryptResult{ "test-secret": mockResult, }, nil) }, @@ -354,7 +354,7 @@ func TestSecretsService_Decrypt_ServiceIdentityContext(t *testing.T) { mockDecryptSvc := &mocks.MockDecryptService{} exposedValue := secretv1beta1.NewExposedSecureValue("test-data") - mockResult := service.NewDecryptResultValue(&exposedValue) + mockResult := secret.NewDecryptResultValue(&exposedValue) // Create a more detailed context matcher to verify the service identity context is created correctly mockDecryptSvc.EXPECT().Decrypt( @@ -364,7 +364,7 @@ func TestSecretsService_Decrypt_ServiceIdentityContext(t *testing.T) { }), "test-namespace", "test-secret", - ).Return(map[string]service.DecryptResult{ + ).Return(map[string]secret.DecryptResult{ "test-secret": mockResult, }, nil) diff --git a/pkg/registry/apis/provisioning/test.go b/pkg/registry/apis/provisioning/test.go index e54388c8eb2..9813c37a0ad 100644 --- a/pkg/registry/apis/provisioning/test.go +++ b/pkg/registry/apis/provisioning/test.go @@ -3,12 +3,14 @@ package provisioning import ( "context" "encoding/json" + "fmt" "net/http" "reflect" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" @@ -43,6 +45,11 @@ func (*testConnector) NewConnectOptions() (runtime.Object, bool, string) { } func (s *testConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + ns, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing namespace") + } + return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := readBody(r, defaultMaxBodySize) if err != nil { @@ -60,6 +67,23 @@ func (s *testConnector) Connect(ctx context.Context, name string, opts runtime.O // In case the body is an empty object if !reflect.ValueOf(cfg).IsZero() { + // HACK: Set the name and namespace if not set so that the temporary repository can be created + // This can be removed once we deprecate legacy secrets is deprecated or we use InLineSecureValues as we + // use the same field and repository name to detect which one to use. + if cfg.GetName() == "" { + if name == "new" { + // HACK: frontend is passing a "new" we need to remove the hack there as well + // Otherwise creation will fail as `new` is a reserved word. Not relevant here as we only "test" + name = "hack-on-hack-for-new" + } + + cfg.SetName(name) + } + + if cfg.GetNamespace() == "" { + cfg.SetNamespace(ns) + } + // Create a temporary repository tmp, err := s.getter.AsRepository(ctx, &cfg) if err != nil { diff --git a/pkg/registry/apis/provisioning/usage.go b/pkg/registry/apis/provisioning/usage.go deleted file mode 100644 index 205a3018662..00000000000 --- a/pkg/registry/apis/provisioning/usage.go +++ /dev/null @@ -1,78 +0,0 @@ -package provisioning - -import ( - "context" - "fmt" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apiserver/pkg/endpoints/request" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/storage/unified/resourcepb" -) - -func (b *APIBuilder) collectProvisioningStats(ctx context.Context) (metrics map[string]any, err error) { - ctx, span := b.tracer.Start(ctx, "Provisioning.Usage.collectProvisioningStats") - defer func() { - span.SetStatus(codes.Error, fmt.Sprintf("failed to fetch provisioning usage stats: %v", err)) - span.End() - }() - - m := map[string]any{} - if b.unified == nil { - span.SetStatus(codes.Ok, "unified storage is not available") - return m, nil - } - - // FIXME: hardcoded to "default" for now -- it works for single tenant deployments - // we could discover the set of valid namespaces, but that would count everything for - // each instance in cloud. - ns := "default" - ctx, _, err = identity.WithProvisioningIdentity(ctx, ns) - if err != nil { - return nil, err - } - ctx = request.WithNamespace(ctx, ns) - - // FIXME: hardcoded to "default" for now -- it works for single tenant deployments - // we could discover the set of valid namespaces, but that would count everything for - // each instance in cloud. - // - // We could get namespaces from the list of repos below, but that could be zero - // while we still have resources managed by terraform, etc - count, err := b.unified.CountManagedObjects(ctx, &resourcepb.CountManagedObjectsRequest{ - Namespace: ns, - }) - if err != nil { - return m, fmt.Errorf("count managed objects: %w", err) - } - counts := make(map[string]int, 10) - for _, v := range count.Items { - counts[v.Kind] = counts[v.Kind] + int(v.Count) - } - - span.SetAttributes(attribute.Int("totalManagedObjectsCount", len(count.Items))) - for k, v := range counts { - m[fmt.Sprintf("stats.managed_by.%s.count", k)] = v - } - - // Inspect all configs - repos, err := b.repositoryLister.List(labels.Everything()) - if err != nil { - return m, fmt.Errorf("list repositories: %w", err) - } - clear(counts) - for _, repo := range repos { - counts[string(repo.Spec.Type)] = counts[string(repo.Spec.Type)] + 1 - } - - span.SetAttributes(attribute.Int("repositoryCount", len(repos))) - // Count how many items of each repository type - for k, v := range counts { - m[fmt.Sprintf("stats.repository.%s.count", k)] = v - } - - return m, nil -} diff --git a/pkg/registry/apis/provisioning/usage/usage.go b/pkg/registry/apis/provisioning/usage/usage.go new file mode 100644 index 00000000000..485c9fe19e4 --- /dev/null +++ b/pkg/registry/apis/provisioning/usage/usage.go @@ -0,0 +1,85 @@ +package usage + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apiserver/pkg/endpoints/request" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + listers "github.com/grafana/grafana/pkg/generated/listers/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" +) + +func MetricCollector(tracer tracing.Tracer, repositoryLister listers.RepositoryLister, unified resource.ResourceClient) usagestats.MetricsFunc { + return func(ctx context.Context) (metrics map[string]any, err error) { + ctx, span := tracer.Start(ctx, "Provisioning.Usage.collectProvisioningStats") + defer func() { + span.SetStatus(codes.Error, fmt.Sprintf("failed to fetch provisioning usage stats: %v", err)) + span.End() + }() + + m := map[string]any{} + if unified == nil { + // FIXME: does this case make any sense? no unified storage -> no game + span.SetStatus(codes.Ok, "unified storage is not available") + return m, nil + } + + // FIXME: hardcoded to "default" for now -- it works for single tenant deployments + // we could discover the set of valid namespaces, but that would count everything for + // each instance in cloud. + ns := "default" + ctx, _, err = identity.WithProvisioningIdentity(ctx, ns) + if err != nil { + return nil, err + } + ctx = request.WithNamespace(ctx, ns) + + // FIXME: hardcoded to "default" for now -- it works for single tenant deployments + // we could discover the set of valid namespaces, but that would count everything for + // each instance in cloud. + // + // We could get namespaces from the list of repos below, but that could be zero + // while we still have resources managed by terraform, etc + count, err := unified.CountManagedObjects(ctx, &resourcepb.CountManagedObjectsRequest{ + Namespace: ns, + }) + if err != nil { + return m, fmt.Errorf("count managed objects: %w", err) + } + counts := make(map[string]int, 10) + for _, v := range count.Items { + counts[v.Kind] = counts[v.Kind] + int(v.Count) + } + + span.SetAttributes(attribute.Int("totalManagedObjectsCount", len(count.Items))) + for k, v := range counts { + m[fmt.Sprintf("stats.managed_by.%s.count", k)] = v + } + + // Inspect all configs + repos, err := repositoryLister.List(labels.Everything()) + if err != nil { + return m, fmt.Errorf("list repositories: %w", err) + } + clear(counts) + for _, repo := range repos { + counts[string(repo.Spec.Type)] = counts[string(repo.Spec.Type)] + 1 + } + + span.SetAttributes(attribute.Int("repositoryCount", len(repos))) + // Count how many items of each repository type + for k, v := range counts { + m[fmt.Sprintf("stats.repository.%s.count", k)] = v + } + + return m, nil + } +} diff --git a/pkg/registry/apis/query/README.md b/pkg/registry/apis/query/README.md index e8d3ba64a7c..d6a0349312c 100644 --- a/pkg/registry/apis/query/README.md +++ b/pkg/registry/apis/query/README.md @@ -1,11 +1,6 @@ # Query service -This query service aims to replace the existing /api/ds/query. - -The key differences are: -1. This service has a stronger type system (not simplejson) -2. Same workflow regardless if expressions exist -3. Datasource settings+access is managed in each datasource, not at the beginning +This query service aims to replace the existing /api/ds/query, while preserving the same parsing and expression handling as `/api/ds/query` @@ -57,22 +52,34 @@ sequenceDiagram autonumber actor User as User or Process participant api as /apis/query.grafana.app - participant ds as Datasource
Handler/Plugin - participant db as Storage
(SQL) + participant db as Storage
(CloudConfig) + participant ds as Datasource
Plugin participant expr as Expression
Engine User->>api: POST Query - api->>api: Parse queries - api->>api: Calculate dependencies - loop Each datasource (concurrently) - api->>ds: QueryData - ds->>ds: Verify user access - ds->>db: Get settings
and secrets + loop Each query + api->>api: Parse query + api->>db: Get ds config
and secrets + db->>api: end - loop Each expression - api->>expr: Execute + alt Expressions exist + api->>expr: Calculate expressions graph + loop Each node (eg, refID) + alt Is query + expr->>ds: QueryData + else Is expression + expr->>expr: Process + end + end + else No expressions + alt Single datasource + api->>ds: QueryData + else Multiple datasources + loop Each datasource (concurrently) + api->>ds: QueryData + end + api->>api: Wait for results + end end - api->>api: Verify ResultExpectations api->>User: return results -``` - +``` \ No newline at end of file diff --git a/pkg/registry/apis/query/client.go b/pkg/registry/apis/query/client.go deleted file mode 100644 index 364e8e5cf9d..00000000000 --- a/pkg/registry/apis/query/client.go +++ /dev/null @@ -1,21 +0,0 @@ -package query - -import ( - "context" - - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" -) - -type CommonDataSourceClientSupplier struct { - Client clientapi.QueryDataClient -} - -func (s *CommonDataSourceClientSupplier) GetDataSourceClient(_ context.Context, _ data.DataSourceRef, _ map[string]string, _ clientapi.InstanceConfigurationSettings) (clientapi.QueryDataClient, error) { - return s.Client, nil -} - -func (s *CommonDataSourceClientSupplier) GetInstanceConfigurationSettings(_ context.Context) (clientapi.InstanceConfigurationSettings, error) { - // FIXME: for now it's an empty structure, we'll find a way to fill it correctly. - return clientapi.InstanceConfigurationSettings{}, nil -} diff --git a/pkg/registry/apis/query/client/plugin.go b/pkg/registry/apis/query/client/plugin.go index a13976fe6b8..3cd805919d1 100644 --- a/pkg/registry/apis/query/client/plugin.go +++ b/pkg/registry/apis/query/client/plugin.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/adapters" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" @@ -65,7 +64,7 @@ var k8sNotFoundError error = &apierrors.StatusError{ } // NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack -func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.QueryDataClient { +func newQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.QueryDataClient { return &pluginClient{ pluginClient: p, pCtxProvider: ctx, @@ -110,34 +109,7 @@ func getGrafanaDataSourceSettings(ctx context.Context) (*backend.DataSourceInsta return adapters.ModelToInstanceSettings(ds, decryptFunc) } -func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*clientapi.Response, error) { - // middlewares may set response-http-headers through context, so we need to do extra steps - // first we create an isolated context to be used by query-data - isolatedCtx := contexthandler.CopyWithReqContext(ctx) - - qdr, err := d.innerQueryData(isolatedCtx, req) - - if err != nil { - return nil, err - } - - rsp := &clientapi.Response{ - QDR: qdr, - Headers: nil, - } - - // we extract the response-headers from the isolated context, and return them explicitly - // in the clientapi.Response structure - reqCtx := contexthandler.FromContext(isolatedCtx) - if reqCtx != nil { - rsp.Headers = reqCtx.Resp.Header() - } - - return rsp, nil -} - -// ExecuteQueryData implements QueryHelper. -func (d *pluginClient) innerQueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) { queries, dsRef, err := data.ToDataSourceQueries(req) if err != nil { return nil, err diff --git a/pkg/registry/apis/query/client/supplier.go b/pkg/registry/apis/query/client/supplier.go new file mode 100644 index 00000000000..08c462d466c --- /dev/null +++ b/pkg/registry/apis/query/client/supplier.go @@ -0,0 +1,44 @@ +package client + +import ( + "context" + + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" + "github.com/grafana/grafana/pkg/setting" +) + +type singleTenantClientSupplier struct { + client clientapi.QueryDataClient + features featuremgmt.FeatureToggles + cfg *setting.Cfg +} + +func NewSingleTenantClientSupplier(cfg *setting.Cfg, features featuremgmt.FeatureToggles, p plugins.Client, ctxProv *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.DataSourceClientSupplier { + return &singleTenantClientSupplier{ + cfg: cfg, + features: features, + client: newQueryClientForPluginClient(p, ctxProv, accessControl), + } +} + +func (s *singleTenantClientSupplier) GetDataSourceClient(_ context.Context, _ data.DataSourceRef, _ map[string]string, _ clientapi.InstanceConfigurationSettings) (clientapi.QueryDataClient, error) { + return s.client, nil +} + +func (s *singleTenantClientSupplier) GetInstanceConfigurationSettings(ctx context.Context) (clientapi.InstanceConfigurationSettings, error) { + return clientapi.InstanceConfigurationSettings{ + StackID: 0, + FeatureToggles: s.features, + FullConfig: nil, + Options: nil, + SQLExpressionCellLimit: s.cfg.SQLExpressionCellLimit, + SQLExpressionOutputCellLimit: s.cfg.SQLExpressionOutputCellLimit, + SQLExpressionTimeout: s.cfg.SQLExpressionTimeout, + ExpressionsEnabled: s.cfg.ExpressionsEnabled, + }, nil +} diff --git a/pkg/registry/apis/query/clientapi/clientapi.go b/pkg/registry/apis/query/clientapi/clientapi.go index 73f1ff129ae..7e83565dbea 100644 --- a/pkg/registry/apis/query/clientapi/clientapi.go +++ b/pkg/registry/apis/query/clientapi/clientapi.go @@ -3,6 +3,7 @@ package clientapi import ( "context" "net/http" + "time" "github.com/grafana/grafana-plugin-sdk-go/backend" data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" @@ -15,14 +16,18 @@ type Response struct { } type QueryDataClient interface { - QueryData(ctx context.Context, req data.QueryDataRequest) (*Response, error) + QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) } type InstanceConfigurationSettings struct { - StackID uint32 - FeatureToggles featuremgmt.FeatureToggles - FullConfig map[string]map[string]string // configuration file settings - Options map[string]string // additional settings related to an instance as set by grafana + StackID uint32 + FeatureToggles featuremgmt.FeatureToggles + FullConfig map[string]map[string]string // configuration file settings + Options map[string]string // additional settings related to an instance as set by grafana + SQLExpressionCellLimit int64 + SQLExpressionOutputCellLimit int64 + SQLExpressionTimeout time.Duration + ExpressionsEnabled bool } type DataSourceClientSupplier interface { diff --git a/pkg/registry/apis/query/connections.go b/pkg/registry/apis/query/connections.go index 9d660de2029..cfe290d3eef 100644 --- a/pkg/registry/apis/query/connections.go +++ b/pkg/registry/apis/query/connections.go @@ -82,6 +82,10 @@ type connectionsProvider struct { registry queryV0.DataSourceApiServerRegistry } +var ( + _ DataSourceConnectionProvider = (*connectionsProvider)(nil) +) + func (q *connectionsProvider) GetConnection(ctx context.Context, namespace string, name string) (*queryV0.DataSourceConnection, error) { info, err := authlib.ParseNamespace(namespace) if err != nil { diff --git a/pkg/registry/apis/query/errors.go b/pkg/registry/apis/query/errors.go index ddfc1ae84ee..9a3de09cf79 100644 --- a/pkg/registry/apis/query/errors.go +++ b/pkg/registry/apis/query/errors.go @@ -2,7 +2,6 @@ package query import ( "errors" - "fmt" "github.com/grafana/grafana/pkg/apimachinery/errutil" ) @@ -44,40 +43,6 @@ func MakePublicQueryError(refID, err string) error { return QueryError.Build(data) } -var depErrStr = "did not execute expression [{{ .Public.refId }}] due to a failure of the dependent expression or query [{{.Public.depRefId}}]" - -var dependencyError = errutil.BadRequest("sse.dependencyError").MustTemplate( - depErrStr, - errutil.WithPublic(depErrStr)) - -func makeDependencyError(refID, depRefID string) error { - data := errutil.TemplateData{ - Public: map[string]interface{}{ - "refId": refID, - "depRefId": depRefID, - }, - Error: fmt.Errorf("did not execute expression %v due to a failure of the dependent expression or query %v", refID, depRefID), - } - - return dependencyError.Build(data) -} - -var cyclicErrStr = "cyclic reference in expression [{{ .Public.refId }}]" - -var cyclicErr = errutil.BadRequest("sse.cyclic").MustTemplate( - cyclicErrStr, - errutil.WithPublic(cyclicErrStr)) - -func makeCyclicError(refID string) error { - data := errutil.TemplateData{ - Public: map[string]interface{}{ - "refId": refID, - }, - Error: fmt.Errorf("cyclic reference in %s", refID), - } - return cyclicErr.Build(data) -} - type ErrorWithRefID struct { err error refId string diff --git a/pkg/registry/apis/query/parser.go b/pkg/registry/apis/query/parser.go deleted file mode 100644 index 96089f763d6..00000000000 --- a/pkg/registry/apis/query/parser.go +++ /dev/null @@ -1,259 +0,0 @@ -package query - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - "gonum.org/v1/gonum/graph/simple" - "gonum.org/v1/gonum/graph/topo" - - query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" - "github.com/grafana/grafana/pkg/expr" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/datasources/service" -) - -type datasourceRequest struct { - // The type - PluginId string `json:"pluginId"` - - // The UID - UID string `json:"uid"` - - // Optionally show the additional query properties - Request *data.QueryDataRequest `json:"request"` - - // Headers that should be forwarded to the next request - Headers map[string]string `json:"headers,omitempty"` -} - -type parsedRequestInfo struct { - // Datasource queries, one for each datasource - Requests []datasourceRequest `json:"requests,omitempty"` - - // Expressions in required execution order - Expressions []expr.ExpressionQuery `json:"expressions,omitempty"` - - // Expressions include explicit hacks for influx+prometheus - RefIDTypes map[string]string `json:"types,omitempty"` - - // Hidden queries used as dependencies - HideBeforeReturn []string `json:"hide,omitempty"` - - // SQL Inputs - SqlInputs map[string]struct{} `json:"sqlInputs,omitempty"` -} - -type queryParser struct { - legacy service.LegacyDataSourceLookup - reader *expr.ExpressionQueryReader - tracer tracing.Tracer - logger log.Logger -} - -func newQueryParser(reader *expr.ExpressionQueryReader, legacy service.LegacyDataSourceLookup, tracer tracing.Tracer, logger log.Logger) *queryParser { - return &queryParser{ - reader: reader, - legacy: legacy, - tracer: tracer, - logger: logger, - } -} - -// Split the main query into multiple -func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRequest) (parsedRequestInfo, error) { - ctx, span := p.tracer.Start(ctx, "QueryService.parseRequest") - defer span.End() - - queryRefIDs := make(map[string]*data.DataQuery, len(input.Queries)) - expressions := make(map[string]*expr.ExpressionQuery) - index := make(map[string]int) // index lookup - rsp := parsedRequestInfo{ - RefIDTypes: make(map[string]string, len(input.Queries)), - SqlInputs: make(map[string]struct{}), - } - - for _, q := range input.Queries { - _, found := queryRefIDs[q.RefID] - if found { - return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId") - } - _, found = expressions[q.RefID] - if found { - return rsp, MakePublicQueryError(q.RefID, "multiple queries with same refId") - } - - ds, err := p.getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID) - if err != nil { - p.logger.Error("Failed to get valid datasource ref", "error", err) - return rsp, err - } - - // Process each query - // check if ds is expression - if expr.IsDataSource(ds.UID) { - // In order to process the query as a typed expression query, we - // are writing it back to JSON and parsing again. Alternatively we - // could construct it from the untyped map[string]any additional properties - // but this approach lets us focus on well typed behavior first - raw, err := json.Marshal(q) - if err != nil { - p.logger.Error("Failed to marshal query for expression", "error", err) - return rsp, err - } - iter, err := jsoniter.ParseBytes(jsoniter.ConfigDefault, raw) - if err != nil { - p.logger.Error("Failed to parse bytes for expression", "error", err) - return rsp, err - } - exp, err := p.reader.ReadQuery(q, iter) - if err != nil { - p.logger.Error("Failed to read query for expression", "error", err) - return rsp, NewErrorWithRefID(q.RefID, err) - } - exp.GraphID = int64(len(expressions) + 1) - expressions[q.RefID] = &exp - } else { - key := fmt.Sprintf("%s/%s", ds.Type, ds.UID) - idx, ok := index[key] - if !ok { - idx = len(index) - index[key] = idx - rsp.Requests = append(rsp.Requests, datasourceRequest{ - PluginId: ds.Type, - UID: ds.UID, - Request: &data.QueryDataRequest{ - TimeRange: getTimeRangeForQuery(&input.TimeRange, q.TimeRange), - Debug: input.Debug, - // no queries - }, - }) - } - - req := rsp.Requests[idx].Request - req.Queries = append(req.Queries, q) - queryRefIDs[q.RefID] = &req.Queries[len(req.Queries)-1] - } - - // Mark all the queries that should be hidden () - if q.Hide { - rsp.HideBeforeReturn = append(rsp.HideBeforeReturn, q.RefID) - } - } - - // Make sure all referenced variables exist and the expression order is stable - if len(expressions) > 0 { - queryNode := &expr.ExpressionQuery{ - GraphID: -1, - } - - // Build the graph for a request - dg := simple.NewDirectedGraph() - dg.AddNode(queryNode) - - for _, exp := range expressions { - dg.AddNode(exp) - } - - for _, exp := range expressions { - vars := exp.Command.NeedsVars() - - for _, refId := range vars { - target := queryNode - q, ok := queryRefIDs[refId] - - if !ok { - if target, ok = expressions[refId]; !ok { - return rsp, makeDependencyError(exp.RefID, refId) - } - } - - // If the input is SQL, conversion is handled differently - if _, isSqlExp := exp.Command.(*expr.SQLCommand); isSqlExp { - if _, ifDepIsAlsoExpression := expressions[refId]; ifDepIsAlsoExpression { - // Only allow data source nodes as SQL expression inputs for now - return rsp, fmt.Errorf("only data source queries may be inputs to a sql expression, %v is the input for %v", refId, exp.RefID) - } else { - rsp.SqlInputs[refId] = struct{}{} - } - } - - // Do not hide queries used in variables - if q != nil && q.Hide { - q.Hide = false - } - if target.ID() == exp.ID() { - return rsp, makeCyclicError(refId) - } - dg.SetEdge(dg.NewEdge(target, exp)) - } - } - - // Add the sorted expressions - sortedNodes, err := topo.SortStabilized(dg, nil) - if err != nil { - p.logger.Error("Error when sorting nodes", "error", err) - return rsp, makeCyclicError("") - } - for _, v := range sortedNodes { - if v.ID() > 0 { - rsp.Expressions = append(rsp.Expressions, *v.(*expr.ExpressionQuery)) - } - } - } - return rsp, nil -} - -func getTimeRangeForQuery(parentTimerange, queryTimerange *data.TimeRange) data.TimeRange { - if queryTimerange != nil && queryTimerange.From != "" && queryTimerange.To != "" { - return *queryTimerange - } - if parentTimerange != nil && parentTimerange.To != "" && parentTimerange.From != "" { - return *parentTimerange - } - return data.TimeRange{ - From: "0", - To: "0", - } -} - -func (p *queryParser) getValidDataSourceRef(ctx context.Context, ds *data.DataSourceRef, id int64) (*data.DataSourceRef, error) { - if ds == nil { - if id == 0 { - return nil, fmt.Errorf("missing datasource reference or id") - } - if p.legacy == nil { - return nil, fmt.Errorf("legacy datasource lookup unsupported (id:%d)", id) - } - return p.legacy.GetDataSourceFromDeprecatedFields(ctx, "", id) - } - - // we need to special-case the "grafana" data source - if ds.UID == "grafana" { - return &data.DataSourceRef{ - // it does not really matter what `type` we set here, - // we will always detect this case by `uid` later. - // here we go with what the data source's plugin.json says. - Type: "grafana", - UID: "grafana", - }, nil - } - - if ds.Type == "" { - if ds.UID == "" { - return nil, fmt.Errorf("missing name/uid in data source reference") - } - if expr.IsDataSource(ds.UID) { - return ds, nil - } - if p.legacy == nil { - return nil, fmt.Errorf("legacy datasource lookup unsupported (name:%s)", ds.UID) - } - return p.legacy.GetDataSourceFromDeprecatedFields(ctx, ds.UID, 0) - } - return ds, nil -} diff --git a/pkg/registry/apis/query/parser_test.go b/pkg/registry/apis/query/parser_test.go deleted file mode 100644 index 8524a863168..00000000000 --- a/pkg/registry/apis/query/parser_test.go +++ /dev/null @@ -1,398 +0,0 @@ -package query - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "os" - "path" - "strings" - "testing" - - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" - "github.com/grafana/grafana/pkg/expr" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/featuremgmt" -) - -type parserTestObject struct { - Description string `json:"description,omitempty"` - Request query.QueryDataRequest `json:"input"` - Expect parsedRequestInfo `json:"expect"` - Error string `json:"error,omitempty"` -} - -func TestQuerySplitting(t *testing.T) { - ctx := context.Background() - parser := newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), - &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), log.NewNopLogger()) - - t.Run("missing datasource flavors", func(t *testing.T) { - split, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - }, - }}, - }, - }) - require.Error(t, err) // Missing datasource - require.Empty(t, split.Requests) - }) - - t.Run("applies zero time range if time range is missing", func(t *testing.T) { - split, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{}, // missing - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }}, - }, - }) - require.NoError(t, err) - require.Len(t, split.Requests, 1) - require.Equal(t, "0", split.Requests[0].Request.From) - require.Equal(t, "0", split.Requests[0].Request.To) - }) - - t.Run("forbid duplicate refId", func(t *testing.T) { - _, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{}, - Queries: []data.DataQuery{ - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - }, - }, - }) - require.Error(t, err) - }) - t.Run("forbid duplicate refId, when refId=''", func(t *testing.T) { - _, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{}, - Queries: []data.DataQuery{ - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - }, - }, - }) - require.Error(t, err) - }) - t.Run("allow empty refId", func(t *testing.T) { - _, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{}, - Queries: []data.DataQuery{ - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - { - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "B", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - }, - }, - }, - }, - }) - require.NoError(t, err) - }) - t.Run("applies query time range if present", func(t *testing.T) { - split, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{}, // missing - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - TimeRange: &data.TimeRange{ - From: "now-1d", - To: "now", - }, - }, - }}, - }, - }) - require.NoError(t, err) - require.Len(t, split.Requests, 1) - require.Equal(t, "now-1d", split.Requests[0].Request.From) - require.Equal(t, "now", split.Requests[0].Request.To) - }) - - t.Run("applies query time range if all time ranges are present", func(t *testing.T) { - split, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - TimeRange: data.TimeRange{ - From: "now-1h", - To: "now", - }, - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - Type: "x", - UID: "abc", - }, - TimeRange: &data.TimeRange{ - From: "now-1d", - To: "now", - }, - }, - }}, - }, - }) - require.NoError(t, err) - require.Len(t, split.Requests, 1) - require.Equal(t, "now-1d", split.Requests[0].Request.From) - require.Equal(t, "now", split.Requests[0].Request.To) - }) - t.Run("verify tests", func(t *testing.T) { - files, err := os.ReadDir("testdata") - require.NoError(t, err) - - for _, file := range files { - if !strings.HasSuffix(file.Name(), ".json") { - continue - } - - t.Run(file.Name(), func(t *testing.T) { - fpath := path.Join("testdata", file.Name()) - // nolint:gosec - body, err := os.ReadFile(fpath) - require.NoError(t, err) - harness := &parserTestObject{} - err = json.Unmarshal(body, harness) - require.NoError(t, err) - - changed := false - parsed, err := parser.parseRequest(ctx, &harness.Request) - if err != nil { - if !assert.Equal(t, harness.Error, err.Error(), "File %s", file) { - changed = true - } - } else { - x, _ := json.Marshal(parsed) - y, _ := json.Marshal(harness.Expect) - if !assert.JSONEq(t, string(y), string(x), "File %s", file) { - changed = true - } - } - - if changed { - harness.Error = "" - harness.Expect = parsed - if err != nil { - harness.Error = err.Error() - } - jj, err := json.MarshalIndent(harness, "", " ") - require.NoError(t, err) - err = os.WriteFile(fpath, jj, 0600) - require.NoError(t, err) - } - }) - } - }) -} - -func TestSqlInputs(t *testing.T) { - parser := newQueryParser( - expr.NewExpressionQueryReader(featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)), - nil, - tracing.InitializeTracerForTest(), - log.NewNopLogger(), - ) - - parsedRequestInfo, err := parser.parseRequest(context.Background(), &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - Queries: []data.DataQuery{ - data.NewDataQuery(map[string]any{ - "refId": "A", - "datasource": &data.DataSourceRef{ - Type: "prometheus", - UID: "local-prom", - }, - }), - data.NewDataQuery(map[string]any{ - "refId": "B", - "datasource": &data.DataSourceRef{ - Type: "__expr__", - UID: "__expr__", - }, - "type": "sql", - "expression": "Select time, value + 10 from A", - }), - }, - }, - }) - require.NoError(t, err) - require.Equal(t, parsedRequestInfo.SqlInputs["B"], struct{}{}) -} - -func TestSqlCTE(t *testing.T) { - parser := newQueryParser( - expr.NewExpressionQueryReader(featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)), - nil, - tracing.InitializeTracerForTest(), - log.NewNopLogger(), - ) - - parsedRequestInfo, err := parser.parseRequest(context.Background(), &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - Queries: []data.DataQuery{ - data.NewDataQuery(map[string]any{ - "refId": "A", - "datasource": &data.DataSourceRef{ - Type: "prometheus", - UID: "local-prom", - }, - }), - data.NewDataQuery(map[string]any{ - "refId": "B", - "datasource": &data.DataSourceRef{ - Type: "__expr__", - UID: "__expr__", - }, - "type": "sql", - "expression": `WITH CTE AS ( - SELECT - Month - FROM A - ) - - SELECT * FROM CTE`, - }), - }, - }, - }) - require.NoError(t, err) - require.Equal(t, parsedRequestInfo.SqlInputs["B"], struct{}{}) -} - -func TestGrafanaDS(t *testing.T) { - ctx := context.Background() - parser := newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), - &noLegacyRetriever{}, tracing.InitializeTracerForTest(), log.NewNopLogger()) - - t.Run("grafana ds without type", func(t *testing.T) { - parsed, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - UID: "grafana", - }, - }, - }}, - }, - }) - require.NoError(t, err) - require.Len(t, parsed.Requests, 1) - require.Equal(t, "grafana", parsed.Requests[0].PluginId) - require.Equal(t, "grafana", parsed.Requests[0].UID) - }) - - t.Run("grafana ds with different type", func(t *testing.T) { - parsed, err := parser.parseRequest(ctx, &query.QueryDataRequest{ - QueryDataRequest: data.QueryDataRequest{ - Queries: []data.DataQuery{{ - CommonQueryProperties: data.CommonQueryProperties{ - RefID: "A", - Datasource: &data.DataSourceRef{ - UID: "grafana", - Type: "datasource", - }, - }, - }}, - }, - }) - require.NoError(t, err) - require.Len(t, parsed.Requests, 1) - require.Equal(t, "grafana", parsed.Requests[0].PluginId) - require.Equal(t, "grafana", parsed.Requests[0].UID) - }) -} - -type noLegacyRetriever struct{} - -var errNoLegacy = errors.New("legacy dds retriever reached, it should not") - -func (s *noLegacyRetriever) GetDataSourceFromDeprecatedFields(ctx context.Context, name string, id int64) (*data.DataSourceRef, error) { - return nil, errNoLegacy -} - -type legacyDataSourceRetriever struct{} - -func (s *legacyDataSourceRetriever) GetDataSourceFromDeprecatedFields(ctx context.Context, name string, id int64) (*data.DataSourceRef, error) { - if id == 100 { - return &data.DataSourceRef{ - Type: "plugin-aaaa", - UID: "AAA", - }, nil - } - if name != "" { - return &data.DataSourceRef{ - Type: "plugin-bbb", - UID: name, - }, nil - } - return nil, fmt.Errorf("missing parameter") -} diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index 47dace1b27f..66a1db38eae 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -2,31 +2,32 @@ package query import ( "context" - "errors" - "fmt" + "encoding/json" "net/http" "slices" "strconv" - "time" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" - "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/mtdsclient" + "github.com/grafana/grafana/pkg/setting" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" - "golang.org/x/sync/errgroup" errorsK8s "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/grafana/pkg/apimachinery/identity" query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" - "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/infra/log" + ds_service "github.com/grafana/grafana/pkg/services/datasources/service" + service "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/web" ) @@ -35,6 +36,32 @@ type queryREST struct { builder *QueryAPIBuilder } +type MyCacheService struct { + legacy ds_service.LegacyDataSourceLookup +} + +func (mcs *MyCacheService) GetDatasource(ctx context.Context, datasourceID int64, _ identity.Requester, _ bool) (*datasources.DataSource, error) { + ref, err := mcs.legacy.GetDataSourceFromDeprecatedFields(ctx, "", datasourceID) + if err != nil { + return nil, err + } + return &datasources.DataSource{ + UID: ref.UID, + Type: ref.Type, + }, nil +} + +func (mcs *MyCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, _ identity.Requester, _ bool) (*datasources.DataSource, error) { + ref, err := mcs.legacy.GetDataSourceFromDeprecatedFields(ctx, datasourceUID, 0) + if err != nil { + return nil, err + } + return &datasources.DataSource{ + UID: ref.UID, + Type: ref.Type, + }, nil +} + var ( _ rest.Storage = (*queryREST)(nil) _ rest.SingularNameProvider = (*queryREST)(nil) @@ -147,65 +174,12 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O responder.Error(err) return } - // Parses the request and splits it into multiple sub queries (if necessary) - req, err := b.parser.parseRequest(ctx, raw) + + qdr, err := handleQuery(ctx, *raw, *b, httpreq, *responder) + if err != nil { - var refError ErrorWithRefID - statusCode := http.StatusBadRequest - message := err - refID := "A" - - if errors.Is(err, datasources.ErrDataSourceNotFound) { - statusCode = http.StatusNotFound - message = errors.New("datasource not found") - } - - if errors.As(err, &refError) { - refID = refError.refId - } - - qdr := &query.QueryDataResponse{ - QueryDataResponse: backend.QueryDataResponse{ - Responses: backend.Responses{ - refID: { - Error: message, - Status: backend.Status(statusCode), - }, - }, - }, - } - - b.log.Error("Error parsing query", "refId", refID, "message", message) - - responder.Object(statusCode, qdr) - return - } - - logEmptyRefids(raw.Queries, b.log) - - for i := range req.Requests { - req.Requests[i].Headers = ExtractKnownHeaders(httpreq.Header) - } - - // Fetch information on the grafana instance (e.g. feature toggles) - instanceConfig, err := b.clientSupplier.GetInstanceConfigurationSettings(ctx) - if err != nil { - msg := "failed to get instance configuration settings" - b.log.Error(msg, "err", err) - responder.Error(errors.New(msg)) - return - } - - // Actually run the query (includes expressions) - rsp, err := b.execute(ctx, req, instanceConfig) - if err != nil { - // we extract the QDR, but the response may be nil - var qdr *backend.QueryDataResponse - if rsp != nil { - qdr = rsp.QDR - } - b.log.Error("execute error", "http code", query.GetResponseCode(qdr), "err", err) + logEmptyRefids(raw.Queries, b.log) if qdr != nil { // if we have a response, we assume the err is set in the response responder.Object(query.GetResponseCode(qdr), &query.QueryDataResponse{ QueryDataResponse: *qdr, @@ -219,335 +193,78 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O } } - // response headers are communicated back using the context for some reason - reqCtx := contexthandler.FromContext(ctx) - if reqCtx != nil { - mergeHeaders(reqCtx.Resp.Header(), rsp.Headers, b.log) - } - - responder.Object(query.GetResponseCode(rsp.QDR), &query.QueryDataResponse{ - QueryDataResponse: *rsp.QDR, // wrap the backend response as a QueryDataResponse + responder.Object(query.GetResponseCode(qdr), &query.QueryDataResponse{ + QueryDataResponse: *qdr, // wrap the backend response as a QueryDataResponse }) }), nil } -func logEmptyRefids(queries []v0alpha1.DataQuery, logger log.Logger) { - emptyCount := 0 - - for _, q := range queries { - if q.RefID == "" { - emptyCount += 1 - } - } - - if emptyCount > 0 { - logger.Info("empty refid found", "empty_count", emptyCount, "query_count", len(queries)) - } -} - -func mergeHeaders(main http.Header, extra http.Header, l log.Logger) { - for headerName, extraValues := range extra { - mainValues := main.Values(headerName) - for _, extraV := range extraValues { - if !slices.Contains(mainValues, extraV) { - main.Add(headerName, extraV) - } else { - l.Warn("skipped duplicate response header", "header", headerName, "value", extraV) - } - } - } -} - -func (b *QueryAPIBuilder) execute(ctx context.Context, req parsedRequestInfo, instanceConfig clientapi.InstanceConfigurationSettings) (*clientapi.Response, error) { - var rsp *clientapi.Response - var err error - switch len(req.Requests) { - case 0: - b.log.Debug("executing empty query") - rsp = &clientapi.Response{QDR: &backend.QueryDataResponse{}} - case 1: - b.log.Debug("executing single query") - rsp, err = b.handleQuerySingleDatasource(ctx, req.Requests[0], instanceConfig) +func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper) (*backend.QueryDataResponse, error) { + var jsonQueries = make([]*simplejson.Json, 0, len(raw.Queries)) + for _, query := range raw.Queries { + jsonBytes, err := json.Marshal(query) if err != nil { - b.log.Debug("handleQuerySingleDatasource failed", err) + b.log.Error("error marshalling", err) } - if err == nil && isSingleAlertQuery(req) { - b.log.Debug("handling alert query with single query") - rsp.QDR, err = b.convertQueryFromAlerting(ctx, req.Requests[0], rsp.QDR) - if err != nil { - b.log.Debug("convertQueryFromAlerting failed", "err", err) - } - } - default: - b.log.Debug("executing concurrent queries") - rsp, err = b.executeConcurrentQueries(ctx, req.Requests, instanceConfig) + + sjQuery, _ := simplejson.NewJson(jsonBytes) if err != nil { - b.log.Debug("error in executeConcurrentQueries", "err", err) + b.log.Error("error unmarshalling", err) } + + jsonQueries = append(jsonQueries, sjQuery) } + mReq := dtos.MetricRequest{ + From: raw.From, + To: raw.To, + Queries: jsonQueries, + } + + cache := &MyCacheService{ + legacy: b.legacyDatasourceLookup, + } + + headers := ExtractKnownHeaders(httpreq.Header) + + instanceConfig, err := b.clientSupplier.GetInstanceConfigurationSettings(ctx) if err != nil { - b.log.Debug("error in query phase, skipping expressions", "error", err) - return rsp, err //return early here to prevent expressions from being executed if we got an error during the query phase + b.log.Error("failed to get instance configuration settings", "err", err) + responder.Error(err) + return nil, err } - if len(req.Expressions) > 0 { - b.log.Debug("executing expressions") - rsp.QDR, err = b.handleExpressions(ctx, req, rsp.QDR) - if err != nil { - b.log.Debug("handleExpressions failed", "err", err) - } - } - - // Remove hidden results - for _, refId := range req.HideBeforeReturn { - r, ok := rsp.QDR.Responses[refId] - if ok && r.Error == nil { - delete(rsp.QDR.Responses, refId) - } - } - - return rsp, err -} - -// Process a single request -// See: https://github.com/grafana/grafana/blob/v10.2.3/pkg/services/query/query.go#L242 -func (b *QueryAPIBuilder) handleQuerySingleDatasource(ctx context.Context, req datasourceRequest, instanceConfig clientapi.InstanceConfigurationSettings) (*clientapi.Response, error) { - ctx, span := b.tracer.Start(ctx, "Query.handleQuerySingleDatasource") - defer span.End() - span.SetAttributes( - attribute.String("datasource.type", req.PluginId), - attribute.String("datasource.uid", req.UID), - ) - - allHidden := true - for idx := range req.Request.Queries { - if !req.Request.Queries[idx].Hide { - allHidden = false - break - } - } - if allHidden { - return &clientapi.Response{}, nil - } - - client, err := b.clientSupplier.GetDataSourceClient( + mtDsClientBuilder := mtdsclient.NewMtDatasourceClientBuilderWithClientSupplier( + b.clientSupplier, ctx, - v0alpha1.DataSourceRef{ - Type: req.PluginId, - UID: req.UID, - }, - req.Headers, + headers, instanceConfig, + b.log, ) + + exprService := expr.ProvideService( + &setting.Cfg{ + ExpressionsEnabled: instanceConfig.ExpressionsEnabled, + SQLExpressionCellLimit: instanceConfig.SQLExpressionCellLimit, + SQLExpressionOutputCellLimit: instanceConfig.SQLExpressionOutputCellLimit, + SQLExpressionTimeout: instanceConfig.SQLExpressionTimeout, + }, + nil, + nil, + instanceConfig.FeatureToggles, + nil, + b.tracer, + mtDsClientBuilder, + ) + + qdr, err := service.QueryData(ctx, b.log, cache, exprService, mReq, mtDsClientBuilder, headers) + if err != nil { - b.log.Debug("error getting single datasource client", "error", err, "reqUid", req.UID) - qdr := buildErrorResponse(err, req) return qdr, err } - rsp, err := client.QueryData(ctx, *req.Request) - - if err == nil && rsp != nil { - for _, q := range req.Request.Queries { - if q.ResultAssertions != nil { - result, ok := rsp.QDR.Responses[q.RefID] - if ok && result.Error == nil { - err = q.ResultAssertions.Validate(result.Frames) - if err != nil { - b.log.Error("Validate failed", "err", err) - result.Error = err - result.ErrorSource = backend.ErrorSourceDownstream - rsp.QDR.Responses[q.RefID] = result - } - } - } - } - } - - if err != nil { - b.log.Debug("error in single datasource query", "error", err) - rsp = buildErrorResponse(err, req) - } - - return rsp, err -} - -// buildErrorResponses applies the provided error to each query response in the list. These queries should all belong to the same datasource. -func buildErrorResponse(err error, req datasourceRequest) *clientapi.Response { - rsp := backend.NewQueryDataResponse() - for _, query := range req.Request.Queries { - rsp.Responses[query.RefID] = backend.DataResponse{ - Error: err, - } - } - return &clientapi.Response{QDR: rsp, Headers: nil} -} - -// executeConcurrentQueries executes queries to multiple datasources concurrently and returns the aggregate result. -func (b *QueryAPIBuilder) executeConcurrentQueries(ctx context.Context, requests []datasourceRequest, instanceConfig clientapi.InstanceConfigurationSettings) (*clientapi.Response, error) { - ctx, span := b.tracer.Start(ctx, "Query.executeConcurrentQueries") - defer span.End() - - g, ctx := errgroup.WithContext(ctx) - g.SetLimit(b.concurrentQueryLimit) // prevent too many concurrent requests - rchan := make(chan *clientapi.Response, len(requests)) - - // Create panic recovery function for loop below - recoveryFn := func(req datasourceRequest) { - if r := recover(); r != nil { - var err error - b.log.Error("query datasource panic", "error", r, "stack", log.Stack(1)) - if theErr, ok := r.(error); ok { - err = theErr - } else if theErrString, ok := r.(string); ok { - err = errors.New(theErrString) - } else { - err = fmt.Errorf("unexpected error - %s", b.userFacingDefaultError) - } - // Due to the panic, there is no valid response for any query for this datasource. Append an error for each one. - rchan <- buildErrorResponse(err, req) - } - } - - // Query each datasource concurrently - for idx := range requests { - req := requests[idx] - g.Go(func() error { - defer recoveryFn(req) - - rsp, err := b.handleQuerySingleDatasource(ctx, req, instanceConfig) - if err == nil { - rchan <- rsp - } else { - rchan <- buildErrorResponse(err, req) - } - return nil - }) - } - - if err := g.Wait(); err != nil { - return nil, err - } - close(rchan) - - // Merge the results from each response - rsp := &clientapi.Response{ - QDR: backend.NewQueryDataResponse(), - Headers: http.Header{}, - } - for result := range rchan { - for refId, dataResponse := range result.QDR.Responses { - rsp.QDR.Responses[refId] = dataResponse - } - - mergeHeaders(rsp.Headers, result.Headers, b.log) - } - - return rsp, nil -} - -// Unlike the implementation in expr/node.go, all datasource queries have been processed first -func (b *QueryAPIBuilder) handleExpressions(ctx context.Context, req parsedRequestInfo, data *backend.QueryDataResponse) (qdr *backend.QueryDataResponse, err error) { - start := time.Now() - ctx, span := b.tracer.Start(ctx, "Query.handleExpressions") - traceId := span.SpanContext().TraceID() - expressionsLogger := b.log.New("traceId", traceId.String()) - expressionsLogger.Debug("handling expressions") - defer func() { - var respStatus string - switch err { - case nil: - respStatus = "success" - default: - respStatus = "failure" - } - duration := float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond) - b.metrics.ExpressionsQuerySummary.WithLabelValues(respStatus).Observe(duration) - - span.End() - }() - - qdr = data - if qdr == nil { - qdr = &backend.QueryDataResponse{} - } - if qdr.Responses == nil { - qdr.Responses = make(backend.Responses) // avoid NPE for lookup - } - now := start // <<< this should come from the original query parser - vars := make(mathexp.Vars) - for _, expression := range req.Expressions { - // Setup the variables - for _, refId := range expression.Command.NeedsVars() { - _, ok := vars[refId] - if !ok { - dr, ok := qdr.Responses[refId] - if ok { - _, isSqlInput := req.SqlInputs[refId] - - _, res, err := b.converter.Convert(ctx, req.RefIDTypes[refId], dr.Frames, isSqlInput) - if err != nil { - expressionsLogger.Error("error converting frames for expressions", "error", err) - res.Error = err - } - - vars[refId] = res - } else { - expressionsLogger.Error("missing variable in handle expressions", "refId", refId, "expressionRefId", expression.RefID) - // This should error in the parsing phase - err := fmt.Errorf("missing variable %s for %s", refId, expression.RefID) - qdr.Responses[refId] = backend.DataResponse{ - Error: err, - } - return qdr, err - } - } - } - - refId := expression.RefID - results, err := expression.Command.Execute(ctx, now, vars, b.tracer, b.metrics) - if err != nil { - expressionsLogger.Error("error executing expression", "error", err) - results.Error = err - } - qdr.Responses[refId] = backend.DataResponse{ - Error: results.Error, - Frames: results.Values.AsDataFrames(refId), - } - } return qdr, nil } -func (b *QueryAPIBuilder) convertQueryFromAlerting(ctx context.Context, req datasourceRequest, - qdr *backend.QueryDataResponse) (*backend.QueryDataResponse, error) { - if len(req.Request.Queries) == 0 { - return nil, errors.New("no queries to convert") - } - if qdr == nil { - b.log.Debug("unexpected response of nil from datasource", "datasource.type", req.PluginId, "datasource.uid", req.UID) - return nil, errors.New("unexpected response of nil from datasource") - } - refID := req.Request.Queries[0].RefID - if _, exist := qdr.Responses[refID]; !exist { - return nil, fmt.Errorf("refID '%s' does not exist", refID) - } - frames := qdr.Responses[refID].Frames - _, results, err := b.converter.Convert(ctx, req.PluginId, frames, false) - if err != nil { - b.log.Error("issue converting query from alerting", "err", err) - results.Error = err - } - qdr = &backend.QueryDataResponse{ - Responses: map[string]backend.DataResponse{ - refID: { - Frames: results.Values.AsDataFrames(refID), - Error: results.Error, - }, - }, - } - return qdr, err -} - type responderWrapper struct { wrapped rest.Responder onObjectFn func(statusCode *int, obj runtime.Object) @@ -578,15 +295,29 @@ func (r responderWrapper) Error(err error) { r.wrapped.Error(err) } -// Checks if the request only contains a single query and is from Alerting -func isSingleAlertQuery(req parsedRequestInfo) bool { - if len(req.Requests) != 1 { - return false +func logEmptyRefids(queries []v0alpha1.DataQuery, logger log.Logger) { + emptyCount := 0 + + for _, q := range queries { + if q.RefID == "" { + emptyCount += 1 + } } - headers := req.Requests[0].Headers - _, exist := headers[models.FromAlertHeaderName] - if exist && len(req.Requests[0].Request.Queries) == 1 { - return true + + if emptyCount > 0 { + logger.Info("empty refid found", "empty_count", emptyCount, "query_count", len(queries)) + } +} + +func mergeHeaders(main http.Header, extra http.Header, l log.Logger) { + for headerName, extraValues := range extra { + mainValues := main.Values(headerName) + for _, extraV := range extraValues { + if !slices.Contains(mainValues, extraV) { + main.Add(headerName, extraV) + } else { + l.Warn("skipped duplicate response header", "header", headerName, "value", extraV) + } + } } - return false } diff --git a/pkg/registry/apis/query/query_test.go b/pkg/registry/apis/query/query_test.go index f6c6b631e85..530e3a5d6b6 100644 --- a/pkg/registry/apis/query/query_test.go +++ b/pkg/registry/apis/query/query_test.go @@ -3,163 +3,274 @@ package query import ( "bytes" "context" - "fmt" + "encoding/json" "net/http" "net/http/httptest" + "os" + "path/filepath" + "strings" "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/grafana/grafana-plugin-sdk-go/backend" - frameData "github.com/grafana/grafana-plugin-sdk-go/data" - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana-plugin-sdk-go/data" + dataapi "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + queryapi "github.com/grafana/grafana/pkg/apis/query/v0alpha1" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" ) -func TestQueryRestConnectHandler(t *testing.T) { - b := &QueryAPIBuilder{ - clientSupplier: mockClient{ - lastCalledWithHeaders: &map[string]string{}, - }, - tracer: tracing.InitializeTracerForTest(), - parser: newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), - &legacyDataSourceRetriever{}, tracing.InitializeTracerForTest(), nil), - log: log.New("test"), +func loadTestdataFrames(t *testing.T, filename string) *backend.QueryDataResponse { + t.Helper() + + // Validate filename doesn't contain path traversal + if strings.Contains(filename, "..") || strings.Contains(filename, "/") { + t.Fatalf("Invalid test filename: %s", filename) } - qr := newQueryREST(b) - ctx := context.Background() - mr := mockResponder{} - handler, err := qr.Connect(ctx, "name", nil, mr) - require.NoError(t, err) + testdataPath := filepath.Join("testdata", filename) + data, err := os.ReadFile(testdataPath) // #nosec G304 -- testdata files in tests + require.NoError(t, err, "Failed to read testdata file: %s", filename) - rr := httptest.NewRecorder() - body := runtime.RawExtension{ - Raw: []byte(`{ - "queries": [ - { - "datasource": { - "type": "prometheus", - "uid": "demo-prometheus" - }, - "expr": "sum(go_gc_duration_seconds)", - "range": false, - "instant": true - } - ], - "from": "now-1h", - "to": "now"}`), - } - req := httptest.NewRequest(http.MethodGet, "/some-path", bytes.NewReader(body.Raw)) - req.Header.Set(models.FromAlertHeaderName, "true") - req.Header.Set(models.CacheSkipHeaderName, "true") - req.Header.Set("X-Rule-Name", "name-1") - req.Header.Set("X-Rule-Uid", "abc") - req.Header.Set("X-Rule-Folder", "folder-1") - req.Header.Set("X-Rule-Source", "grafana-ruler") - req.Header.Set("X-Rule-Type", "type-1") - req.Header.Set("X-Rule-Version", "version-1") - req.Header.Set("X-Grafana-Org-Id", "1") - req.Header.Set("Content-Type", "application/json") - req.Header.Set("some-unexpected-header", "some-value") - handler.ServeHTTP(rr, req) + var result *backend.QueryDataResponse + err = json.Unmarshal(data, &result) + require.NoError(t, err, "Failed to unmarshal testdata file: %s", filename) - require.Equal(t, map[string]string{ - models.FromAlertHeaderName: "true", - models.CacheSkipHeaderName: "true", - "X-Rule-Name": "name-1", - "X-Rule-Uid": "abc", - "X-Rule-Folder": "folder-1", - "X-Rule-Source": "grafana-ruler", - "X-Rule-Type": "type-1", - "X-Rule-Version": "version-1", - "X-Grafana-Org-Id": "1", - }, *b.clientSupplier.(mockClient).lastCalledWithHeaders) + return result } -func TestInstantQueryFromAlerting(t *testing.T) { - builder := &QueryAPIBuilder{ - converter: &expr.ResultConverter{ - Features: featuremgmt.WithFeatures(), - Tracer: tracing.InitializeTracerForTest(), +func TestQueryAPI(t *testing.T) { + testCases := []struct { + name string + queryJSON string + headers map[string]string + expectedStatus int + testdataFile string + stubbedFrame *data.Frame + }{ + { + name: "single prometheus query", + queryJSON: `{ + "queries": [ + { + "datasource": { + "type": "prometheus", + "uid": "demo-prom" + }, + "expr": "1 + 6", + "range": false, + "instant": true, + "refId": "A" + } + ], + "from": "now-1h", + "to": "now" + }`, + expectedStatus: http.StatusOK, + testdataFile: "single_prometheus_query.json", + stubbedFrame: data.NewFrame("", + data.NewField("Time", nil, []time.Time{time.Unix(1704067200, 0)}), + data.NewField("Value", nil, []float64{7.0}), + ), + }, + + { + name: "prometheus query with server side expression", + queryJSON: `{ + "queries": [ + { + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "demo-prom" + }, + "expr": "7", + "range": false, + "instant": true, + "hide": true + }, + { + "refId": "B", + "datasource": { + "uid": "__expr__", + "type": "__expr__" + }, + "type": "math", + "expression": "$A * 3" + } + ], + "from": "now-1h", + "to": "now" + }`, + testdataFile: "prometheus_with_sse.json", + expectedStatus: http.StatusOK, + stubbedFrame: data.NewFrame("", + data.NewField("Value", nil, []float64{7.0}), + ), + }, + + { + name: "prometheus query with sql expression and hidden prom query", + queryJSON: `{ + "queries": [ + { + "datasource": { + "type": "prometheus", + "uid": "demo-prom" + }, + "expr": "1 + 2", + "range": false, + "instant": true, + "refId": "A", + "hidden": true + }, + { + "datasource": { + "uid": "__expr__", + "type": "__expr__" + }, + "type": "sql", + "expression": "Select Value + 10 from A;", + "refId": "B" + } + ], + "from": "now-1h", + "to": "now" + }`, + testdataFile: "prometheus_with_sql_expression.json", + expectedStatus: http.StatusOK, + stubbedFrame: data.NewFrame("", + data.NewField("Time", nil, []time.Time{time.Unix(1704067200, 0)}), + data.NewField("Value", nil, []float64{7.0}), + ), }, } - dq := data.DataQuery{} - dq.RefID = "A" - - dr := datasourceRequest{ - Headers: map[string]string{ - models.FromAlertHeaderName: "true", - }, - Request: &data.QueryDataRequest{ - Queries: []data.DataQuery{ - dq, - }, - }, - } - - fakeFrame := frameData.NewFrame( - "A", - frameData.NewField("Time", nil, []time.Time{time.Now()}), - frameData.NewField("Value", nil, []int64{42}), - ) - fakeFrame.Meta = &frameData.FrameMeta{TypeVersion: frameData.FrameTypeVersion{0, 1}, Type: "numeric-multi"} - - inputQDR := &backend.QueryDataResponse{ - Responses: map[string]backend.DataResponse{ - "A": { - Frames: frameData.Frames{ - fakeFrame, + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := &QueryAPIBuilder{ + converter: &expr.ResultConverter{ + Features: featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions), + Tracer: tracing.InitializeTracerForTest(), }, - }, - }, + clientSupplier: mockClient{ + stubbedFrame: tc.stubbedFrame, + }, + tracer: tracing.InitializeTracerForTest(), + log: log.New("test"), + legacyDatasourceLookup: &mockLegacyDataSourceLookup{}, + } + + req := httptest.NewRequest(http.MethodPost, "/some-path", bytes.NewReader([]byte(tc.queryJSON))) + req.Header.Set("Content-Type", "application/json") + + // Set optional headers + for key, value := range tc.headers { + req.Header.Set(key, value) + } + + ctx := context.Background() + mr := &mockResponder{} + qr := newQueryREST(builder) + + handler, err := qr.Connect(ctx, "name", nil, mr) + require.NoError(t, err) + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + require.NoError(t, mr.err, "Should not have error in responder") + require.Equal(t, tc.expectedStatus, mr.statusCode, "Should return expected status code") + require.NotNil(t, mr.response, "Should have a response object") + + // Verify the response is the expected type + qdr, ok := mr.response.(*queryapi.QueryDataResponse) + require.True(t, ok, "Response should be QueryDataResponse type") + require.NotNil(t, qdr.Responses, "Should have responses") + + // Load expected frames from testdata if provided + if tc.testdataFile != "" { + expectedResponse := loadTestdataFrames(t, tc.testdataFile) + + // get refids from expected response + expectedRefIds := make([]string, 0, len(expectedResponse.Responses)) + for refID := range expectedResponse.Responses { + expectedRefIds = append(expectedRefIds, refID) + } + + // Verify all expected refIDs are present + for _, refID := range expectedRefIds { + require.Contains(t, qdr.Responses, refID, "Should contain response for refId %s", refID) + + actualResponse := qdr.Responses[refID] + expectedFrameResponse := expectedResponse.Responses[refID] + + // Verify frame structure matches testdata + require.Len(t, actualResponse.Frames, len(expectedFrameResponse.Frames), "Frame count should match testdata for refId %s", refID) + + for i, actualFrame := range actualResponse.Frames { + expectedFrame := expectedFrameResponse.Frames[i] + if diff := cmp.Diff(expectedFrame, actualFrame, data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s", diff) + } + } + } + } else { + t.Fatalf("No testdata file provided for test case %s", tc.name) + } + + t.Logf("Test case '%s' completed successfully", tc.name) + }) } - - request := parsedRequestInfo{ - Requests: []datasourceRequest{ - dr, - }, - } - - result, err := builder.convertQueryFromAlerting(context.Background(), dr, inputQDR) - require.NoError(t, err) - - require.True(t, isSingleAlertQuery(request), "Expected a valid alert query with a single query to return true") - require.NotNil(t, result) - require.Equal(t, 1, len(result.Responses["A"].Frames[0].Fields), "Expected a single field not Time and Value") - require.Equal(t, "Value", result.Responses["A"].Frames[0].Fields[0].Name, "Expected the single field to be Value") } type mockResponder struct { + statusCode int + response runtime.Object + err error } // Object writes the provided object to the response. Invoking this method multiple times is undefined. -func (m mockResponder) Object(statusCode int, obj runtime.Object) { +func (m *mockResponder) Object(statusCode int, obj runtime.Object) { + m.statusCode = statusCode + m.response = obj } // Error writes the provided error to the response. This method may only be invoked once. -func (m mockResponder) Error(err error) { +func (m *mockResponder) Error(err error) { + m.err = err } type mockClient struct { - lastCalledWithHeaders *map[string]string + stubbedFrame *data.Frame } -func (m mockClient) GetDataSourceClient(ctx context.Context, ref data.DataSourceRef, headers map[string]string, instanceConfig clientapi.InstanceConfigurationSettings) (clientapi.QueryDataClient, error) { - *m.lastCalledWithHeaders = headers - - return nil, fmt.Errorf("mock error") +func (m mockClient) GetDataSourceClient(ctx context.Context, ref dataapi.DataSourceRef, headers map[string]string, instanceConfig clientapi.InstanceConfigurationSettings) (clientapi.QueryDataClient, error) { + mclient := mockClient{ + stubbedFrame: m.stubbedFrame, + } + return mclient, nil } -func (m mockClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - return nil, fmt.Errorf("mock error") +func (m mockClient) QueryData(ctx context.Context, req dataapi.QueryDataRequest) (*backend.QueryDataResponse, error) { + responses := make(backend.Responses) + for i := range req.Queries { + refID := req.Queries[i].RefID + frame := m.stubbedFrame + frame.RefID = refID + responses[refID] = backend.DataResponse{ + Status: backend.StatusOK, + Frames: []*data.Frame{frame}, + } + } + return &backend.QueryDataResponse{ + Responses: responses, + }, nil } func (m mockClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { @@ -170,8 +281,20 @@ func (m mockClient) CheckHealth(ctx context.Context, req *backend.CheckHealthReq return nil, nil } -func (m mockClient) GetInstanceConfigurationSettings(_ context.Context) (clientapi.InstanceConfigurationSettings, error) { - return clientapi.InstanceConfigurationSettings{}, nil +func (m mockClient) GetInstanceConfigurationSettings(ctx context.Context) (clientapi.InstanceConfigurationSettings, error) { + return clientapi.InstanceConfigurationSettings{ + ExpressionsEnabled: true, + FeatureToggles: featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions), + }, nil +} + +type mockLegacyDataSourceLookup struct{} + +func (m *mockLegacyDataSourceLookup) GetDataSourceFromDeprecatedFields(ctx context.Context, name string, id int64) (*dataapi.DataSourceRef, error) { + return &dataapi.DataSourceRef{ + UID: "demo-prom", + Type: "prometheus", + }, nil } func TestMergeHeaders(t *testing.T) { diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index 9bb95bafbaf..18a7d2b2a9d 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -31,39 +31,38 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" + "github.com/grafana/grafana/pkg/setting" ) var _ builder.APIGroupBuilder = (*QueryAPIBuilder)(nil) type QueryAPIBuilder struct { - log log.Logger - concurrentQueryLimit int - userFacingDefaultError string - features featuremgmt.FeatureToggles + log log.Logger + concurrentQueryLimit int + features featuremgmt.FeatureToggles authorizer authorizer.Authorizer - tracer tracing.Tracer - metrics *metrics.ExprMetrics - parser *queryParser - clientSupplier clientapi.DataSourceClientSupplier - connections DataSourceConnectionProvider - registry query.DataSourceApiServerRegistry - converter *expr.ResultConverter - queryTypes *query.QueryTypeDefinitionList + tracer tracing.Tracer + metrics *metrics.ExprMetrics + clientSupplier clientapi.DataSourceClientSupplier + registry query.DataSourceApiServerRegistry + converter *expr.ResultConverter + queryTypes *query.QueryTypeDefinitionList + legacyDatasourceLookup service.LegacyDataSourceLookup + connections DataSourceConnectionProvider } -func NewQueryAPIBuilder(features featuremgmt.FeatureToggles, +func NewQueryAPIBuilder( + features featuremgmt.FeatureToggles, clientSupplier clientapi.DataSourceClientSupplier, ar authorizer.Authorizer, registry query.DataSourceApiServerRegistry, - connections DataSourceConnectionProvider, - legacy service.LegacyDataSourceLookup, registerer prometheus.Registerer, tracer tracing.Tracer, + legacyDatasourceLookup service.LegacyDataSourceLookup, + connections DataSourceConnectionProvider, ) (*QueryAPIBuilder, error) { - reader := expr.NewExpressionQueryReader(features) - // Include well typed query definitions var queryTypes *query.QueryTypeDefinitionList if features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes) { @@ -85,7 +84,6 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles, clientSupplier: clientSupplier, authorizer: ar, registry: registry, - parser: newQueryParser(reader, legacy, tracer, log.New("query_parser")), metrics: metrics.NewQueryServiceExpressionsMetrics(registerer), tracer: tracer, features: features, @@ -95,10 +93,13 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles, Features: features, Tracer: tracer, }, + legacyDatasourceLookup: legacyDatasourceLookup, }, nil } -func RegisterAPIService(features featuremgmt.FeatureToggles, +func RegisterAPIService( + cfg *setting.Cfg, + features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, dataSourcesService datasources.DataSourceService, pluginStore pluginstore.Store, @@ -107,7 +108,8 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, pCtxProvider *plugincontext.Provider, registerer prometheus.Registerer, tracer tracing.Tracer, - legacy service.LegacyDataSourceLookup, + legacyDatasourceLookup service.LegacyDataSourceLookup, + exprService *expr.Service, ) (*QueryAPIBuilder, error) { if !featuremgmt.AnyEnabled(features, featuremgmt.FlagQueryService, @@ -129,13 +131,16 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, }) reg := client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService) + builder, err := NewQueryAPIBuilder( features, - &CommonDataSourceClientSupplier{ - Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider, accessControl), - }, - ar, reg, - &connectionsProvider{dsService: dataSourcesService, registry: reg}, legacy, registerer, tracer, + client.NewSingleTenantClientSupplier(cfg, features, pluginClient, pCtxProvider, accessControl), + ar, + client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService), + registerer, + tracer, + legacyDatasourceLookup, + &connectionsProvider{dsService: dataSourcesService, registry: reg}, ) apiregistration.RegisterAPI(builder) return builder, err diff --git a/pkg/registry/apis/query/testdata/cyclic-references.json b/pkg/registry/apis/query/testdata/cyclic-references.json deleted file mode 100644 index b181f6b0468..00000000000 --- a/pkg/registry/apis/query/testdata/cyclic-references.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "description": "self dependencies", - "input": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "A", - "datasource": { - "type": "", - "uid": "__expr__" - }, - "expression": "$B", - "type": "math" - }, - { - "refId": "B", - "datasource": { - "type": "", - "uid": "__expr__" - }, - "type": "math", - "expression": "$A" - } - ] - }, - "expect": {}, - "error": "[sse.cyclic] cyclic reference in expression []" -} \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/multiple-uids-same-plugin.json b/pkg/registry/apis/query/testdata/multiple-uids-same-plugin.json deleted file mode 100644 index 68a6705f094..00000000000 --- a/pkg/registry/apis/query/testdata/multiple-uids-same-plugin.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "input": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "A", - "datasource": { - "type": "plugin-x", - "uid": "123" - } - }, - { - "refId": "B", - "datasource": { - "type": "plugin-x", - "uid": "456" - } - } - ] - }, - "expect": { - "requests": [ - { - "pluginId": "plugin-x", - "uid": "123", - "request": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "A", - "datasource": { - "type": "plugin-x", - "uid": "123" - } - } - ] - } - }, - { - "pluginId": "plugin-x", - "uid": "456", - "request": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "B", - "datasource": { - "type": "plugin-x", - "uid": "456" - } - } - ] - } - } - ] - } -} \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/prometheus_with_sql_expression.json b/pkg/registry/apis/query/testdata/prometheus_with_sql_expression.json new file mode 100644 index 00000000000..040de2f397f --- /dev/null +++ b/pkg/registry/apis/query/testdata/prometheus_with_sql_expression.json @@ -0,0 +1,32 @@ +{ + "results": { + "B": { + "status": 200, + "frames": [ + { + "schema": { + "name": "B", + "refId": "B", + "fields": [ + { + "name": "Value + 10", + "type": "number", + "typeInfo": { + "frame": "float64", + "nullable": false + } + } + ] + }, + "data": { + "values": [ + [ + 17 + ] + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/prometheus_with_sse.json b/pkg/registry/apis/query/testdata/prometheus_with_sse.json new file mode 100644 index 00000000000..d0785eb77e9 --- /dev/null +++ b/pkg/registry/apis/query/testdata/prometheus_with_sse.json @@ -0,0 +1,39 @@ +{ + "results": { + + "B": { + "status": 200, + "frames": [ + { + "schema": { + "refId": "B", + "fields": [ + { + "name": "B", + "type": "number", + "typeInfo": { + "frame": "float64", + "nullable": true + } + } + ], + "meta": { + "type": "numeric-multi", + "typeVersion": [ + 0, + 1 + ] + } + }, + "data": { + "values": [ + [ + 21 + ] + ] + } + } + ] + } + } + } \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/self-reference.json b/pkg/registry/apis/query/testdata/self-reference.json deleted file mode 100644 index 05d709992f9..00000000000 --- a/pkg/registry/apis/query/testdata/self-reference.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "description": "self dependencies", - "input": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "A", - "datasource": { - "type": "", - "uid": "__expr__" - }, - "type": "math", - "expression": "$A" - } - ] - }, - "expect": {}, - "error": "[sse.cyclic] cyclic reference in expression [A]" -} \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/single_prometheus_query.json b/pkg/registry/apis/query/testdata/single_prometheus_query.json new file mode 100644 index 00000000000..ba4d51b90f3 --- /dev/null +++ b/pkg/registry/apis/query/testdata/single_prometheus_query.json @@ -0,0 +1,41 @@ +{ + "results": { + "A": { + "status": 200, + "frames": [ + { + "schema": { + "refId": "A", + "fields": [ + { + "name": "Time", + "type": "time", + "typeInfo": { + "frame": "time.Time" + } + }, + { + "name": "Value", + "type": "number", + "typeInfo": { + "frame": "float64" + }, + "labels": {} + } + ] + }, + "data": { + "values": [ + [ + 1704067200000 + ], + [ + 7 + ] + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/pkg/registry/apis/query/testdata/with-expressions.json b/pkg/registry/apis/query/testdata/with-expressions.json deleted file mode 100644 index a1cbca89994..00000000000 --- a/pkg/registry/apis/query/testdata/with-expressions.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "description": "one hidden query with two expressions that start out-of-order", - "input": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "C", - "datasource": { - "type": "", - "uid": "__expr__" - }, - "type": "reduce", - "expression": "$B", - "reducer": "last" - }, - { - "refId": "A", - "datasource": { - "type": "sql", - "uid": "123" - }, - "hide": true - }, - { - "refId": "B", - "datasource": { - "type": "", - "uid": "-100" - }, - "type": "math", - "expression": "$A + 10" - } - ] - }, - "expect": { - "requests": [ - { - "pluginId": "sql", - "uid": "123", - "request": { - "from": "now-6", - "to": "now", - "queries": [ - { - "refId": "A", - "datasource": { - "type": "sql", - "uid": "123" - } - } - ] - } - } - ], - "expressions": [ - { - "id": 2, - "refId": "B", - "type": "math", - "properties": { - "expression": "$A + 10" - } - }, - { - "id": 1, - "refId": "C", - "type": "reduce", - "properties": { - "expression": "$B", - "reducer": "last" - } - } - ], - "hide": [ - "A" - ] - } -} \ No newline at end of file diff --git a/pkg/registry/apis/secret/accesscontrol.go b/pkg/registry/apis/secret/accesscontrol.go index 1b4c050fb58..2262961c087 100644 --- a/pkg/registry/apis/secret/accesscontrol.go +++ b/pkg/registry/apis/secret/accesscontrol.go @@ -29,6 +29,25 @@ var ( func registerAccessControlRoles(service accesscontrol.Service) error { // SecureValues + // These are broken down into more granular fixed roles on purpose. + // For inline Secure Values, we want to allow creation and deletion by Editors because there's no API to read/update. + // References are only available with the API and RBAC, so those roles can be granted to any basic role by Operators. + secureValuesCreator := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.securevalues:creator", + DisplayName: "Secure Values Creator", + Description: "Create secure values.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ + { + Action: ActionSecretSecureValuesCreate, + Scope: ScopeAllSecureValues, + }, + }, + }, + Grants: []string{string(org.RoleEditor)}, + } + secureValuesReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.securevalues:reader", @@ -45,32 +64,36 @@ func registerAccessControlRoles(service accesscontrol.Service) error { Grants: []string{string(org.RoleAdmin)}, } - secureValuesWriter := accesscontrol.RoleRegistration{ + secureValuesUpdater := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ - Name: "fixed:secret.securevalues:writer", - DisplayName: "Secure Values Writer", - Description: "Create, update and delete secure values.", + Name: "fixed:secret.securevalues:updater", + DisplayName: "Secure Values Updater", + Description: "Update secure values.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ - { - Action: ActionSecretSecureValuesCreate, - Scope: ScopeAllSecureValues, - }, - { - Action: ActionSecretSecureValuesRead, - Scope: ScopeAllSecureValues, - }, { Action: ActionSecretSecureValuesWrite, Scope: ScopeAllSecureValues, }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + secureValuesDeleter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:secret.securevalues:deleter", + DisplayName: "Secure Values Deleter", + Description: "Delete secure values.", + Group: "Secrets Manager", + Permissions: []accesscontrol.Permission{ { Action: ActionSecretSecureValuesDelete, Scope: ScopeAllSecureValues, }, }, }, - Grants: []string{string(org.RoleAdmin)}, + Grants: []string{string(org.RoleEditor)}, } // Keepers @@ -119,7 +142,11 @@ func registerAccessControlRoles(service accesscontrol.Service) error { } return service.DeclareFixedRoles( - secureValuesReader, secureValuesWriter, - keepersReader, keepersWriter, + secureValuesCreator, + secureValuesReader, + secureValuesUpdater, + secureValuesDeleter, + keepersReader, + keepersWriter, ) } diff --git a/pkg/registry/apis/secret/assert/assert.go b/pkg/registry/apis/secret/assert/assert.go deleted file mode 100644 index 43f9779440e..00000000000 --- a/pkg/registry/apis/secret/assert/assert.go +++ /dev/null @@ -1,26 +0,0 @@ -package assert - -import ( - "errors" - "fmt" -) - -func True(expr bool, msg string, args ...any) { - if !expr { - if len(args) > 0 { - panic(fmt.Sprintf(msg, args...)) - } else { - panic(msg) - } - } -} - -func ErrorIs(err1, err2 error) { - if !errors.Is(err1, err2) { - panic(fmt.Sprintf("expected error %T(%+v) to be %T(%+v)", err1, err1, err2, err2)) - } -} - -func Equal[T comparable](v1 T, v2 T, msg string) { - True(v1 == v2, "expected %+v to equal %+v: %s", v1, v2, msg) -} diff --git a/pkg/registry/apis/secret/assert/assert_test.go b/pkg/registry/apis/secret/assert/assert_test.go deleted file mode 100644 index 35e284ff89f..00000000000 --- a/pkg/registry/apis/secret/assert/assert_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package assert - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestTrue(t *testing.T) { - t.Parallel() - - require.PanicsWithValue(t, "error msg", func() { - True(1 == 2, "error msg") - }) - - require.PanicsWithValue(t, "error msg 1", func() { - True(1 == 2, "error msg %d", 1) - }) - - require.NotPanics(t, func() { - True(true, "oops") - }) -} - -func TestErrorIs(t *testing.T) { - t.Parallel() - - err := errors.New("some error") - - require.PanicsWithValue(t, "expected error *errors.errorString(other error) to be *errors.errorString(some error)", func() { - ErrorIs(fmt.Errorf("other error"), err) - }) - - require.NotPanics(t, func() { - ErrorIs(err, err) - ErrorIs(fmt.Errorf("something: %w", err), err) - }) -} - -func TestEqual(t *testing.T) { - t.Parallel() - - require.PanicsWithValue(t, "expected 1 to equal 2: details", func() { - Equal(1, 2, "details") - }) - - require.NotPanics(t, func() { - Equal("a", "a", "details") - }) -} diff --git a/pkg/registry/apis/secret/contracts/data_key.go b/pkg/registry/apis/secret/contracts/data_key.go index bac21460d18..5ee4bead843 100644 --- a/pkg/registry/apis/secret/contracts/data_key.go +++ b/pkg/registry/apis/secret/contracts/data_key.go @@ -29,7 +29,12 @@ type DataKeyStorage interface { CreateDataKey(ctx context.Context, dataKey *SecretDataKey) error GetDataKey(ctx context.Context, namespace, uid string) (*SecretDataKey, error) GetCurrentDataKey(ctx context.Context, namespace, label string) (*SecretDataKey, error) - GetAllDataKeys(ctx context.Context, namespace string) ([]*SecretDataKey, error) + ListDataKeys(ctx context.Context, namespace string) ([]*SecretDataKey, error) DisableDataKeys(ctx context.Context, namespace string) error DeleteDataKey(ctx context.Context, namespace, uid string) error } + +// GlobalDataKeyStorage is an interface for namespace unbounded operations. +type GlobalDataKeyStorage interface { + DisableAllDataKeys(ctx context.Context) error +} diff --git a/pkg/registry/apis/secret/contracts/decrypt.go b/pkg/registry/apis/secret/contracts/decrypt.go index 13c28b1025c..9c0d8e8eb2b 100644 --- a/pkg/registry/apis/secret/contracts/decrypt.go +++ b/pkg/registry/apis/secret/contracts/decrypt.go @@ -23,3 +23,32 @@ type DecryptStorage interface { type DecryptAuthorizer interface { Authorize(ctx context.Context, secureValueName string, secureValueDecrypters []string) (identity string, allowed bool) } + +// DecryptService is the inferface for the decrypt service. +type DecryptService interface { + Decrypt(ctx context.Context, namespace string, names ...string) (map[string]DecryptResult, error) +} + +// DecryptResult is the (union) result of a decryption operation. +// It contains the decrypted `value` when the decryption succeeds, and the `err` when it fails. +// It is not possible to construct a `DecryptResult` where both `value` and `err` are set from another package. +type DecryptResult struct { + value *secretv1beta1.ExposedSecureValue + err error +} + +func (d DecryptResult) Error() error { + return d.err +} + +func (d DecryptResult) Value() *secretv1beta1.ExposedSecureValue { + return d.value +} + +func NewDecryptResultErr(err error) DecryptResult { + return DecryptResult{err: err} +} + +func NewDecryptResultValue(value *secretv1beta1.ExposedSecureValue) DecryptResult { + return DecryptResult{value: value} +} diff --git a/pkg/registry/apis/secret/contracts/secure_value.go b/pkg/registry/apis/secret/contracts/secure_value.go index 787397d4c31..fa1eae74133 100644 --- a/pkg/registry/apis/secret/contracts/secure_value.go +++ b/pkg/registry/apis/secret/contracts/secure_value.go @@ -21,9 +21,8 @@ type DecryptSecureValue struct { } var ( - ErrSecureValueNotFound = errors.New("secure value not found") - ErrSecureValueAlreadyExists = errors.New("secure value already exists") - ErrSecureValueOperationInProgress = errors.New("an operation is already in progress for the secure value") + ErrSecureValueNotFound = errors.New("secure value not found") + ErrSecureValueAlreadyExists = errors.New("secure value already exists") ) type ReadOpts struct { diff --git a/pkg/registry/apis/secret/decrypt/service.go b/pkg/registry/apis/secret/decrypt/service.go index d35fb6eb2e9..38556c0c4f9 100644 --- a/pkg/registry/apis/secret/decrypt/service.go +++ b/pkg/registry/apis/secret/decrypt/service.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" ) @@ -12,23 +11,21 @@ type OSSDecryptService struct { decryptStore contracts.DecryptStorage } -var _ service.DecryptService = &OSSDecryptService{} - -func ProvideDecryptService(decryptStore contracts.DecryptStorage) service.DecryptService { +func ProvideDecryptService(decryptStore contracts.DecryptStorage) contracts.DecryptService { return &OSSDecryptService{ decryptStore: decryptStore, } } -func (d *OSSDecryptService) Decrypt(ctx context.Context, namespace string, names ...string) (map[string]service.DecryptResult, error) { - results := make(map[string]service.DecryptResult, len(names)) +func (d *OSSDecryptService) Decrypt(ctx context.Context, namespace string, names ...string) (map[string]contracts.DecryptResult, error) { + results := make(map[string]contracts.DecryptResult, len(names)) for _, name := range names { exposedSecureValue, err := d.decryptStore.Decrypt(ctx, xkube.Namespace(namespace), name) if err != nil { - results[name] = service.NewDecryptResultErr(err) + results[name] = contracts.NewDecryptResultErr(err) } else { - results[name] = service.NewDecryptResultValue(&exposedSecureValue) + results[name] = contracts.NewDecryptResultValue(&exposedSecureValue) } } diff --git a/pkg/registry/apis/secret/decrypt/service_test.go b/pkg/registry/apis/secret/decrypt/service_test.go index 0aaac869a67..3203e3ebbe7 100644 --- a/pkg/registry/apis/secret/decrypt/service_test.go +++ b/pkg/registry/apis/secret/decrypt/service_test.go @@ -6,7 +6,7 @@ import ( "testing" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -23,8 +23,8 @@ func TestDecryptService(t *testing.T) { mockErr := errors.New("mock error") mockStorage := &MockDecryptStorage{} mockStorage.On("Decrypt", mock.Anything, mock.Anything, mock.Anything).Return(secretv1beta1.ExposedSecureValue(""), mockErr) - decryptedValuesResp := map[string]service.DecryptResult{ - "secure-value-1": service.NewDecryptResultErr(mockErr), + decryptedValuesResp := map[string]contracts.DecryptResult{ + "secure-value-1": contracts.NewDecryptResultErr(mockErr), } decryptService := &OSSDecryptService{ @@ -49,9 +49,9 @@ func TestDecryptService(t *testing.T) { mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-2"). Return(exposedSecureValue2, nil) - decryptedValuesResp := map[string]service.DecryptResult{ - "secure-value-1": service.NewDecryptResultValue(&exposedSecureValue1), - "secure-value-2": service.NewDecryptResultValue(&exposedSecureValue2), + decryptedValuesResp := map[string]contracts.DecryptResult{ + "secure-value-1": contracts.NewDecryptResultValue(&exposedSecureValue1), + "secure-value-2": contracts.NewDecryptResultValue(&exposedSecureValue2), } decryptService := &OSSDecryptService{ @@ -75,9 +75,9 @@ func TestDecryptService(t *testing.T) { mockStorage.On("Decrypt", mock.Anything, xkube.Namespace("default"), "secure-value-2"). Return(secretv1beta1.ExposedSecureValue(""), mockErr) - decryptedValuesResp := map[string]service.DecryptResult{ - "secure-value-1": service.NewDecryptResultValue(&exposedSecureValue), - "secure-value-2": service.NewDecryptResultErr(mockErr), + decryptedValuesResp := map[string]contracts.DecryptResult{ + "secure-value-1": contracts.NewDecryptResultValue(&exposedSecureValue), + "secure-value-2": contracts.NewDecryptResultErr(mockErr), } decryptService := &OSSDecryptService{ diff --git a/pkg/registry/apis/secret/decrypt_client.go b/pkg/registry/apis/secret/decrypt_client.go new file mode 100644 index 00000000000..0ed0d3b31a7 --- /dev/null +++ b/pkg/registry/apis/secret/decrypt_client.go @@ -0,0 +1,25 @@ +package secret + +import ( + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" +) + +// DecryptService is a decrypt client for secure value secrets. +type DecryptService = contracts.DecryptService + +var ( + ErrDecryptNotFound = contracts.ErrDecryptNotFound + ErrDecryptNotAuthorized = contracts.ErrDecryptNotAuthorized + ErrDecryptFailed = contracts.ErrDecryptFailed +) + +type DecryptResult = contracts.DecryptResult + +func NewDecryptResultErr(err error) DecryptResult { + return contracts.NewDecryptResultErr(err) +} + +func NewDecryptResultValue(value *secretv1beta1.ExposedSecureValue) DecryptResult { + return contracts.NewDecryptResultValue(value) +} diff --git a/pkg/registry/apis/secret/encryption/doc.go b/pkg/registry/apis/secret/encryption/doc.go deleted file mode 100644 index a4ca2a0fd2c..00000000000 --- a/pkg/registry/apis/secret/encryption/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package encryption provides envelope encryption for secrets manager - -// It is heavily copied from the legacy envelope encryption implementation at github.com/grafana/grafana/pkg/services/encryption. - -package encryption diff --git a/pkg/registry/apis/secret/encryption/manager/manager_test.go b/pkg/registry/apis/secret/encryption/manager/manager_test.go index 97166c52267..ede903c06d6 100644 --- a/pkg/registry/apis/secret/encryption/manager/manager_test.go +++ b/pkg/registry/apis/secret/encryption/manager/manager_test.go @@ -45,7 +45,7 @@ func TestEncryptionService_EnvelopeEncryption(t *testing.T) { require.NoError(t, err) assert.Equal(t, plaintext, decrypted) - keys, err := svc.store.GetAllDataKeys(ctx, namespace) + keys, err := svc.store.ListDataKeys(ctx, namespace) require.NoError(t, err) assert.Equal(t, len(keys), 1) }) @@ -60,7 +60,7 @@ func TestEncryptionService_EnvelopeEncryption(t *testing.T) { require.NoError(t, err) assert.Equal(t, plaintext, decrypted) - keys, err := svc.store.GetAllDataKeys(ctx, namespace) + keys, err := svc.store.ListDataKeys(ctx, namespace) require.NoError(t, err) assert.Equal(t, len(keys), 1) }) @@ -139,12 +139,12 @@ func TestEncryptionService_DataKeys(t *testing.T) { }) t.Run("deleting DEK when no id provided must fail", func(t *testing.T) { - beforeDelete, err := store.GetAllDataKeys(ctx, namespace) + beforeDelete, err := store.ListDataKeys(ctx, namespace) require.NoError(t, err) err = store.DeleteDataKey(ctx, namespace, "") require.Error(t, err) - afterDelete, err := store.GetAllDataKeys(ctx, namespace) + afterDelete, err := store.ListDataKeys(ctx, namespace) require.NoError(t, err) assert.Equal(t, beforeDelete, afterDelete) }) diff --git a/pkg/registry/apis/secret/encryption/secrets.go b/pkg/registry/apis/secret/encryption/secrets.go index 36beed76c37..426cbb79543 100644 --- a/pkg/registry/apis/secret/encryption/secrets.go +++ b/pkg/registry/apis/secret/encryption/secrets.go @@ -40,8 +40,3 @@ func (id ProviderID) Kind() (string, error) { func KeyLabel(providerID ProviderID) string { return fmt.Sprintf("%s@%s", time.Now().Format("2006-01-02"), providerID) } - -// BackgroundProvider should be implemented for a provider that has a task that needs to be run in the background. -type BackgroundProvider interface { - Run(ctx context.Context) error -} diff --git a/pkg/registry/apis/secret/secure_value_client.go b/pkg/registry/apis/secret/secure_value_client.go index 612191d3d28..e34f31abd42 100644 --- a/pkg/registry/apis/secret/secure_value_client.go +++ b/pkg/registry/apis/secret/secure_value_client.go @@ -2,20 +2,30 @@ package secret import ( "context" + "errors" "fmt" claims "github.com/grafana/authlib/types" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/client-go/dynamic" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" +) + +var ( + ErrSecureValueNotFound = contracts.ErrSecureValueNotFound + ErrSecureValueAlreadyExists = contracts.ErrSecureValueAlreadyExists ) // SecureValueClient is a CRUD client for the secure value API. @@ -25,14 +35,16 @@ type secureValueClient struct { namespace string service contracts.SecureValueService validator contracts.SecureValueValidator + access authorizer.Authorizer } var _ SecureValueClient = &secureValueClient{} -func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator) SecureValueClient { +func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator, access claims.AccessClient) SecureValueClient { return &secureValueClient{ service: service, validator: validator, + access: authsvc.NewResourceAuthorizer(access), } } @@ -62,6 +74,10 @@ func (c *secureValueClient) Create(ctx context.Context, obj *unstructured.Unstru return nil, fmt.Errorf("namespace is required") } + if err := c.checkAccess(ctx, obj.GetName(), utils.VerbCreate); err != nil { + return nil, err + } + sv, err := fromUnstructured(obj) if err != nil { return nil, err @@ -81,7 +97,7 @@ func (c *secureValueClient) Create(ctx context.Context, obj *unstructured.Unstru createdSv, err := c.service.Create(ctx, sv, user.GetUID()) if err != nil { - return nil, err + return nil, c.mapError(err, sv.Name) } return toUnstructured(createdSv) @@ -96,9 +112,13 @@ func (c *secureValueClient) Get(ctx context.Context, name string, _ metav1.GetOp return nil, fmt.Errorf("name is required") } + if err := c.checkAccess(ctx, name, utils.VerbGet); err != nil { + return nil, err + } + sv, err := c.service.Read(ctx, xkube.Namespace(c.namespace), name) if err != nil { - return nil, err + return nil, c.mapError(err, name) } return toUnstructured(sv) @@ -110,6 +130,10 @@ func (c *secureValueClient) Update(ctx context.Context, obj *unstructured.Unstru return nil, fmt.Errorf("namespace is required") } + if err := c.checkAccess(ctx, obj.GetName(), utils.VerbUpdate); err != nil { + return nil, err + } + oldUnstructured, err := c.Get(ctx, obj.GetName(), metav1.GetOptions{}) if err != nil { return nil, err @@ -139,7 +163,7 @@ func (c *secureValueClient) Update(ctx context.Context, obj *unstructured.Unstru updatedSv, _, err := c.service.Update(ctx, sv, user.GetUID()) if err != nil { - return nil, err + return nil, c.mapError(err, sv.Name) } return toUnstructured(updatedSv) @@ -154,8 +178,12 @@ func (c *secureValueClient) Delete(ctx context.Context, name string, _ metav1.De return fmt.Errorf("name is required") } + if err := c.checkAccess(ctx, name, utils.VerbDelete); err != nil { + return err + } + _, err := c.service.Delete(ctx, xkube.Namespace(c.namespace), name) - return err + return c.mapError(err, name) } // List all secure values in the namespace. Options and subresources are not supported and ignored. @@ -164,9 +192,13 @@ func (c *secureValueClient) List(ctx context.Context, _ metav1.ListOptions) (*un return nil, fmt.Errorf("namespace is required") } + if err := c.checkAccess(ctx, "", utils.VerbList); err != nil { + return nil, err + } + list, err := c.service.List(ctx, xkube.Namespace(c.namespace)) if err != nil { - return nil, err + return nil, c.mapError(err, "") } items := make([]unstructured.Unstructured, 0, len(list.Items)) @@ -214,6 +246,49 @@ func (c *secureValueClient) ApplyStatus(_ context.Context, _ string, _ *unstruct return nil, fmt.Errorf("applyStatus is not supported") } +// Maps an error from the domain to a K8s API Status error. +func (c *secureValueClient) mapError(err error, name string) error { + if err == nil { + return nil + } + + gr := secretv1beta1.SecureValuesResourceInfo.GroupResource() + + switch { + case errors.Is(err, ErrSecureValueNotFound): + return apierrors.NewNotFound(gr, name) + case errors.Is(err, ErrSecureValueAlreadyExists): + return apierrors.NewAlreadyExists(gr, name) + } + + return apierrors.NewInternalError(err) +} + +func (c *secureValueClient) checkAccess(ctx context.Context, name, verb string) error { + gr := secretv1beta1.SecureValuesResourceInfo.GroupResource() + + decision, reason, err := c.access.Authorize(ctx, authorizer.AttributesRecord{ + Verb: verb, + Namespace: c.namespace, + APIGroup: secretv1beta1.APIGroup, + APIVersion: secretv1beta1.APIVersion, + Resource: gr.Resource, + Subresource: "", + Name: name, + ResourceRequest: true, + }) + + if err != nil { + return apierrors.NewForbidden(gr, name, fmt.Errorf("failed to check access: %w", err)) + } + + if decision != authorizer.DecisionAllow { + return apierrors.NewForbidden(gr, name, fmt.Errorf("no access to %s: %s", verb, reason)) + } + + return nil +} + func toUnstructured(sv *secretv1beta1.SecureValue) (*unstructured.Unstructured, error) { unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sv) if err != nil { diff --git a/pkg/registry/apis/secret/secure_value_client_test.go b/pkg/registry/apis/secret/secure_value_client_test.go index 3f67763c329..8f52f5b29cb 100644 --- a/pkg/registry/apis/secret/secure_value_client_test.go +++ b/pkg/registry/apis/secret/secure_value_client_test.go @@ -4,11 +4,11 @@ import ( "testing" "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" "github.com/grafana/grafana/pkg/registry/apis/secret/validator" ) @@ -21,11 +21,15 @@ func TestIntegration_SecureValueClient_CRUD(t *testing.T) { client := ProvideSecureValueClient( setup.SecureValueService, validator, + setup.AccessClient, ) ns := "stacks-1234" ctx := testutils.CreateUserAuthContext(t.Context(), ns, map[string][]string{ - "securevalues:read": {"securevalues:uid:*"}, + "securevalues:create": {"securevalues:uid:*"}, + "securevalues:read": {"securevalues:uid:*"}, + "securevalues:write": {"securevalues:uid:*"}, + "securevalues:delete": {"securevalues:uid:*"}, }) nsClient, err := client.Client(ctx, ns) @@ -104,6 +108,68 @@ func TestIntegration_SecureValueClient_CRUD(t *testing.T) { require.NoError(t, err) read, err = nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{}) - require.ErrorIs(t, err, contracts.ErrSecureValueNotFound) + var apiErr *apierrors.StatusError + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonNotFound) + require.Nil(t, read) +} + +func Test_SecureValueClient_CRUD_NoPermissions(t *testing.T) { + setup := testutils.Setup(t) + + validator := validator.ProvideSecureValueValidator() + + client := ProvideSecureValueClient( + setup.SecureValueService, + validator, + setup.AccessClient, + ) + + ns := "stacks-1234" + ctx := testutils.CreateUserAuthContext(t.Context(), ns, nil) + + nsClient, err := client.Client(ctx, ns) + require.NoError(t, err) + require.NotNil(t, nsClient) + + sv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sv", + Namespace: ns, + }, + } + + unstructured, err := toUnstructured(sv) + require.NoError(t, err) + + // Create + created, err := nsClient.Create(ctx, unstructured, metav1.CreateOptions{}) + var apiErr *apierrors.StatusError + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonForbidden) + require.Nil(t, created) + + // Read + read, err := nsClient.Get(ctx, sv.Name, metav1.GetOptions{}) + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonForbidden) + require.Nil(t, created) + + // Update + updated, err := nsClient.Update(ctx, unstructured, metav1.UpdateOptions{}) + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonForbidden) + require.Nil(t, updated) + + // List + list, err := nsClient.List(ctx, metav1.ListOptions{}) + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonForbidden) + require.Nil(t, list) + + // Delete + err = nsClient.Delete(ctx, sv.Name, metav1.DeleteOptions{}) + require.ErrorAs(t, err, &apiErr) + require.Equal(t, apiErr.ErrStatus.Reason, metav1.StatusReasonForbidden) require.Nil(t, read) } diff --git a/pkg/registry/apis/secret/service/decrypt.go b/pkg/registry/apis/secret/service/decrypt.go deleted file mode 100644 index f8ff83d95c9..00000000000 --- a/pkg/registry/apis/secret/service/decrypt.go +++ /dev/null @@ -1,36 +0,0 @@ -package service - -import ( - "context" - - secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" -) - -// DecryptResult is the (union) result of a decryption operation. -// It contains the decrypted `value` when the decryption succeeds, and the `err` when it fails. -// It is not possible to construct a `DecryptResult` where both `value` and `err` are set from another package. -type DecryptResult struct { - value *secretv1beta1.ExposedSecureValue - err error -} - -func (d DecryptResult) Error() error { - return d.err -} - -func (d DecryptResult) Value() *secretv1beta1.ExposedSecureValue { - return d.value -} - -func NewDecryptResultErr(err error) DecryptResult { - return DecryptResult{err: err} -} - -func NewDecryptResultValue(value *secretv1beta1.ExposedSecureValue) DecryptResult { - return DecryptResult{value: value} -} - -// DecryptService is the inferface for the decrypt service. -type DecryptService interface { - Decrypt(ctx context.Context, namespace string, names ...string) (map[string]DecryptResult, error) -} diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index 5bac9a4d174..478b9c63e84 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -23,7 +23,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/actest" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/secret/database" @@ -70,7 +70,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { require.NoError(t, err) // Initialize access client + access control - accessControl := &actest.FakeAccessControl{ExpectedEvaluate: true} + accessControl := acimpl.ProvideAccessControl(nil) accessClient := accesscontrol.NewLegacyAccessClient(accessControl, accesscontrol.ResourceAuthorizerOptions{ Resource: "securevalues", Attr: "uid", @@ -132,6 +132,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { EncryptedValueStorage: encryptedValueStorage, SQLKeeper: sqlKeeper, Database: database, + AccessClient: accessClient, } } @@ -139,10 +140,11 @@ type Sut struct { SecureValueService contracts.SecureValueService SecureValueMetadataStorage contracts.SecureValueMetadataStorage DecryptStorage contracts.DecryptStorage - DecryptService service.DecryptService + DecryptService contracts.DecryptService EncryptedValueStorage contracts.EncryptedValueStorage SQLKeeper *sqlkeeper.SQLKeeper Database *database.Database + AccessClient types.AccessClient } type CreateSvConfig struct { diff --git a/pkg/registry/apis/secret/tracectx/carrier.go b/pkg/registry/apis/secret/tracectx/carrier.go deleted file mode 100644 index a8153c42d1e..00000000000 --- a/pkg/registry/apis/secret/tracectx/carrier.go +++ /dev/null @@ -1,57 +0,0 @@ -package tracectx - -import ( - "context" - "encoding/hex" - "fmt" - "strings" - - "go.opentelemetry.io/otel/propagation" -) - -const ( - kvSeparator = "=" - pairSeparator = "#" -) - -func HexEncodeTraceFromContext(ctx context.Context) string { - carrier := propagation.MapCarrier(make(map[string]string)) - - propagation.TraceContext{}.Inject(ctx, carrier) - - // no trace in context - if len(carrier) == 0 { - return "" - } - - pairs := make([]string, 0, len(carrier)) - for k, v := range carrier { - pairs = append(pairs, k+kvSeparator+v) - } - - return hex.EncodeToString([]byte(strings.Join(pairs, pairSeparator))) -} - -func HexDecodeTraceIntoContext(ctx context.Context, encoded string) (context.Context, error) { - if encoded == "" { - return ctx, nil - } - - decoded, err := hex.DecodeString(encoded) - if err != nil { - return nil, err - } - - pairs := strings.Split(string(decoded), pairSeparator) - - carrier := make(propagation.MapCarrier, len(pairs)) - for _, pair := range pairs { - kv := strings.SplitN(pair, kvSeparator, 2) - if len(kv) != 2 || kv[0] == "" || kv[1] == "" { - return nil, fmt.Errorf("invalid key-value pair: %s", pair) - } - carrier[kv[0]] = kv[1] - } - - return propagation.TraceContext{}.Extract(ctx, carrier), nil -} diff --git a/pkg/registry/apis/secret/tracectx/carrier_test.go b/pkg/registry/apis/secret/tracectx/carrier_test.go deleted file mode 100644 index 043428faeb2..00000000000 --- a/pkg/registry/apis/secret/tracectx/carrier_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package tracectx - -import ( - "context" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/trace" -) - -func TestHexEncodeTraceFromContext(t *testing.T) { - t.Run("when no trace is present in context, it returns empty string", func(t *testing.T) { - ctx := context.Background() - - encoded := HexEncodeTraceFromContext(ctx) - require.Empty(t, encoded) - }) - - t.Run("when trace is present in context, it returns hex-encoded string", func(t *testing.T) { - carrier := propagation.MapCarrier{ - "traceparent": "00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01", - "tracestate": "first=abc1234,second=xyz7890", - } - ctx := propagation.TraceContext{}.Extract(context.Background(), carrier) - - encoded := HexEncodeTraceFromContext(ctx) - require.NotEmpty(t, encoded) - - traceCtx, err := HexDecodeTraceIntoContext(context.Background(), encoded) - require.NoError(t, err) - - span := trace.SpanFromContext(traceCtx) - require.True(t, span.SpanContext().IsValid()) - - carrier = propagation.MapCarrier(make(map[string]string)) - propagation.TraceContext{}.Inject(traceCtx, carrier) - require.Contains(t, carrier, "traceparent") - require.Contains(t, carrier, "tracestate") - }) -} - -func TestHexDecodeTraceIntoContext(t *testing.T) { - t.Run("when encoded string is empty, it returns original context", func(t *testing.T) { - ctx := context.Background() - - result, err := HexDecodeTraceIntoContext(ctx, "") - require.NoError(t, err) - require.Equal(t, ctx, result) - }) - - t.Run("when encoded string is valid hex, it returns context with trace", func(t *testing.T) { - encoded := hex.EncodeToString([]byte("traceparent=00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01#tracestate=first=abc1234,second=xyz7890")) - - ctx, err := HexDecodeTraceIntoContext(context.Background(), encoded) - require.NoError(t, err) - - span := trace.SpanFromContext(ctx) - require.True(t, span.SpanContext().IsValid()) - }) - - t.Run("when encoded string has invalid hex encoding, it returns an error", func(t *testing.T) { - invalidHex := "invalid-hex-zzz" - - result, err := HexDecodeTraceIntoContext(context.Background(), invalidHex) - require.Error(t, err) - require.Nil(t, result) - }) - - t.Run("when decoded string has invalid key-value pair format, it returns an error", func(t *testing.T) { - // missing key - encoded := hex.EncodeToString([]byte("00-446e31681d64f9dcefd947c95ef321d0-009e2f3d8ded1892-01")) - - result, err := HexDecodeTraceIntoContext(context.Background(), encoded) - require.Error(t, err) - require.Nil(t, result) - }) - - t.Run("when decoded string has key without value, it returns error", func(t *testing.T) { - // missing value - encoded := hex.EncodeToString([]byte("traceparent=")) - - result, err := HexDecodeTraceIntoContext(context.Background(), encoded) - require.Error(t, err) - require.Nil(t, result) - }) -} diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 40f7dfc64de..fa7607102f8 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -5,6 +5,7 @@ import ( "slices" "github.com/grafana/grafana-app-sdk/app" + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "k8s.io/client-go/rest" "github.com/grafana/grafana/pkg/infra/log" @@ -20,6 +21,14 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// ProvideAppInstallers returns a list of app installers that can be used to install apps. +// This is the pattern that should be used to provide app installers in the app registry. +func ProvideAppInstallers( + playlistAppInstaller *playlist.PlaylistAppInstaller, +) []appsdkapiserver.AppInstaller { + return []appsdkapiserver.AppInstaller{playlistAppInstaller} +} + var ( _ registry.BackgroundService = (*Service)(nil) ) @@ -29,12 +38,12 @@ type Service struct { log log.Logger } -// ProvideRegistryServiceSink is an entry point for each service that will force initialization -func ProvideRegistryServiceSink( +// ProvideBuilderRunners adapts apps to the APIGroupBuilder interface. +// deprecated: Use ProvideAppInstallers instead. +func ProvideBuilderRunners( registrar builder.APIRegistrar, restConfigProvider apiserver.RestConfigProvider, features featuremgmt.FeatureToggles, - playlistAppProvider *playlist.PlaylistAppProvider, investigationAppProvider *investigations.InvestigationsAppProvider, advisorAppProvider *advisor.AdvisorAppProvider, alertingNotificationsAppProvider *notifications.AlertingNotificationsAppProvider, @@ -56,7 +65,7 @@ func ProvideRegistryServiceSink( logger := log.New("app-registry") var apiGroupRunner *runner.APIGroupRunner var err error - providers := []app.Provider{playlistAppProvider} + providers := []app.Provider{} if features.IsEnabledGlobally(featuremgmt.FlagInvestigationsBackend) { logger.Debug("Investigations backend is enabled") providers = append(providers, investigationAppProvider) diff --git a/pkg/registry/apps/playlist/register.go b/pkg/registry/apps/playlist/register.go index 5c8a0eeab87..4374c0265dd 100644 --- a/pkg/registry/apps/playlist/register.go +++ b/pkg/registry/apps/playlist/register.go @@ -6,50 +6,65 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" "github.com/grafana/grafana/apps/playlist/pkg/apis" playlistv0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" playlistapp "github.com/grafana/grafana/apps/playlist/pkg/app" "github.com/grafana/grafana/pkg/apimachinery/utils" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - "github.com/grafana/grafana/pkg/services/apiserver/builder/runner" + "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" playlistsvc "github.com/grafana/grafana/pkg/services/playlist" "github.com/grafana/grafana/pkg/setting" ) -type PlaylistAppProvider struct { - app.Provider +var ( + _ appsdkapiserver.AppInstaller = (*PlaylistAppInstaller)(nil) + _ appinstaller.LegacyStorageProvider = (*PlaylistAppInstaller)(nil) + _ appinstaller.APIEnablementProvider = (*PlaylistAppInstaller)(nil) +) + +type PlaylistAppInstaller struct { + appsdkapiserver.AppInstaller cfg *setting.Cfg service playlistsvc.Service } -func RegisterApp( +func RegisterAppInstaller( p playlistsvc.Service, cfg *setting.Cfg, features featuremgmt.FeatureToggles, -) *PlaylistAppProvider { - provider := &PlaylistAppProvider{ +) (*PlaylistAppInstaller, error) { + installer := &PlaylistAppInstaller{ cfg: cfg, service: p, } - appCfg := &runner.AppBuilderConfig{ - OpenAPIDefGetter: playlistv0alpha1.GetOpenAPIDefinitions, - LegacyStorageGetter: provider.legacyStorageGetter, - ManagedKinds: playlistapp.GetKinds(), - CustomConfig: any(&playlistapp.PlaylistConfig{ - EnableReconcilers: features.IsEnabledGlobally(featuremgmt.FlagPlaylistsReconciler), - }), - AllowedV0Alpha1Resources: []string{playlistv0alpha1.PlaylistKind().Plural()}, + specificConfig := any(&playlistapp.PlaylistConfig{ + EnableReconcilers: features.IsEnabledGlobally(featuremgmt.FlagPlaylistsReconciler), + }) + provider := simple.NewAppProvider(apis.LocalManifest(), specificConfig, playlistapp.New) + + appConfig := app.Config{ + KubeConfig: restclient.Config{}, // this will be overridden by the installer's InitializeApp method + ManifestData: *apis.LocalManifest().ManifestData, + SpecificConfig: specificConfig, } - provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, playlistapp.New) - return provider + i, err := appsdkapiserver.NewDefaultAppInstaller(provider, appConfig, apis.ManifestGoTypeAssociator, apis.ManifestCustomRouteResponsesAssociator) + if err != nil { + return nil, err + } + installer.AppInstaller = i + + return installer, nil } -func (p *PlaylistAppProvider) legacyStorageGetter(requested schema.GroupVersionResource) grafanarest.Storage { +// GetLegacyStorage returns the legacy storage for the playlist app. +func (p *PlaylistAppInstaller) GetLegacyStorage(requested schema.GroupVersionResource) grafanarest.Storage { gvr := schema.GroupVersionResource{ Group: playlistv0alpha1.PlaylistKind().Group(), Version: playlistv0alpha1.PlaylistKind().Version(), @@ -87,3 +102,10 @@ func (p *PlaylistAppProvider) legacyStorageGetter(requested schema.GroupVersionR ) return legacyStore } + +// GetAllowedV0Alpha1Resources returns the list of resources that are allowed to be accessed in v0alpha1. +func (p *PlaylistAppInstaller) GetAllowedV0Alpha1Resources() []string { + return []string{ + playlistv0alpha1.PlaylistKind().Plural(), + } +} diff --git a/pkg/registry/apps/wireset.go b/pkg/registry/apps/wireset.go index 5e20014ff9b..48212655418 100644 --- a/pkg/registry/apps/wireset.go +++ b/pkg/registry/apps/wireset.go @@ -10,8 +10,9 @@ import ( ) var WireSet = wire.NewSet( - ProvideRegistryServiceSink, - playlist.RegisterApp, + ProvideAppInstallers, + ProvideBuilderRunners, + playlist.RegisterAppInstaller, investigations.RegisterApp, advisor.RegisterApp, notifications.RegisterApp, diff --git a/pkg/semconv/go.mod b/pkg/semconv/go.mod index 0dad3f7ee25..4e7a041137c 100644 --- a/pkg/semconv/go.mod +++ b/pkg/semconv/go.mod @@ -1,8 +1,8 @@ module github.com/grafana/grafana/pkg/semconv -go 1.24.4 +go 1.24.5 -require go.opentelemetry.io/otel v1.36.0 +require go.opentelemetry.io/otel v1.37.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/pkg/semconv/go.sum b/pkg/semconv/go.sum index 554c1894c03..aca512fb7f1 100644 --- a/pkg/semconv/go.sum +++ b/pkg/semconv/go.sum @@ -6,7 +6,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go index bca9bf21c6b..19cb24db04b 100644 --- a/pkg/server/module_server.go +++ b/pkg/server/module_server.go @@ -193,7 +193,7 @@ func (s *ModuleServer) Run() error { }) m.RegisterModule(modules.FrontendServer, func() (services.Service, error) { - return frontend.ProvideFrontendService(s.cfg, s.promGatherer, s.license) + return frontend.ProvideFrontendService(s.cfg, s.features, s.promGatherer, s.registerer, s.license) }) m.RegisterModule(modules.All, nil) diff --git a/pkg/server/ring.go b/pkg/server/ring.go index cd89f719f8a..90026e58391 100644 --- a/pkg/server/ring.go +++ b/pkg/server/ring.go @@ -91,7 +91,7 @@ func toRingConfig(cfg *setting.Cfg, KVStore kv.Config) ring.Config { rc.KVStore = KVStore rc.HeartbeatTimeout = resource.RingHeartbeatTimeout - rc.ReplicationFactor = 1 + rc.ReplicationFactor = cfg.SearchRingReplicationFactor return rc } diff --git a/pkg/server/search_server_distributor_test.go b/pkg/server/search_server_distributor_test.go index 32dddffcec8..1fce76d8936 100644 --- a/pkg/server/search_server_distributor_test.go +++ b/pkg/server/search_server_distributor_test.go @@ -274,6 +274,7 @@ func initDistributorServerForTest(t *testing.T, memberlistPort int) testModuleSe cfg.MemberlistJoinMember = "127.0.0.1:" + strconv.Itoa(memberlistPort) cfg.MemberlistAdvertiseAddr = "127.0.0.1" cfg.MemberlistAdvertisePort = memberlistPort + cfg.SearchRingReplicationFactor = 1 cfg.Target = []string{modules.SearchServerDistributor} cfg.InstanceID = "distributor" // does nothing for the distributor but may be useful to debug tests @@ -308,6 +309,7 @@ func createStorageServerApi(t *testing.T, instanceId int, dbType, dbConnStr stri cfg.MemberlistJoinMember = "127.0.0.1:" + strconv.Itoa(memberlistPort) cfg.MemberlistAdvertiseAddr = "127.0.0.1" cfg.MemberlistAdvertisePort = getRandomPort() + cfg.SearchRingReplicationFactor = 1 cfg.InstanceID = "instance-" + strconv.Itoa(instanceId) cfg.IndexPath = t.TempDir() + cfg.InstanceID cfg.IndexFileThreshold = testIndexFileThreshold diff --git a/pkg/server/wire.go b/pkg/server/wire.go index d32e3ca6586..741ec53ede8 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -101,6 +101,7 @@ import ( "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/loginattempt/loginattemptimpl" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/navtree/navtreeimpl" "github.com/grafana/grafana/pkg/services/ngalert" ngimage "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -323,6 +324,7 @@ var wireBasicSet = wire.NewSet( serviceaccountsmanager.ProvideServiceAccountsService, serviceaccountsproxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*serviceaccountsproxy.ServiceAccountsProxy)), + mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 47e11549107..440ede3c8b9 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -143,6 +143,7 @@ import ( "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/loginattempt/loginattemptimpl" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/navtree/navtreeimpl" "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -575,8 +576,9 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider) + mtDatasourceClientBuilder := mtdsclient.NewNullMTDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, mtDatasourceClientBuilder) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, mtDatasourceClientBuilder) repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider) if err != nil { @@ -682,7 +684,13 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser scopedPluginDatasourceProvider := datasource.ProvideDefaultPluginConfigs(service15, cacheServiceImpl, plugincontextProvider) v := builder.ProvideDefaultBuildHandlerChainFuncFromBuilders() aggregatorRunner := aggregatorrunner.ProvideNoopAggregatorConfigurator() - apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner) + playlistAppInstaller, err := playlist.RegisterAppInstaller(playlistService, cfg, featureToggles) + if err != nil { + return nil, err + } + v2 := appregistry.ProvideAppInstallers(playlistAppInstaller) + builderMetrics := builder.ProvideBuilderMetrics(registerer) + apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { return nil, err } @@ -695,12 +703,11 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService) - playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles) investigationsAppProvider := investigations.RegisterApp(cfg) checkregistryService := checkregistry.ProvideService(service15, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, noop, provisionedpluginsNoop, ssosettingsimplService, cfg, pluginerrsStore) advisorAppProvider := advisor2.RegisterApp(checkregistryService, cfg) alertingNotificationsAppProvider := notifications2.RegisterApp(cfg, alertNG) - appregistryService, err := appregistry.ProvideRegistryServiceSink(apiserverService, eventualRestConfigProvider, featureToggles, playlistAppProvider, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg) + appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg) if err != nil { return nil, err } @@ -733,7 +740,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } legacyDataSourceLookup := service7.ProvideLegacyDataSourceLookup(service15) - queryAPIBuilder, err := query2.RegisterAPIService(featureToggles, apiserverService, service15, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup) + queryAPIBuilder, err := query2.RegisterAPIService(cfg, featureToggles, apiserverService, service15, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup, exprService) if err != nil { return nil, err } @@ -775,17 +782,17 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser } secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) secureValueValidator := validator3.ProvideSecureValueValidator() - secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator) + secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator, accessClient) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer) if err != nil { return nil, err } - decryptService := decrypt.ProvideDecryptService(decryptStorage) - repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, decryptService) + v3 := decrypt.ProvideDecryptService(decryptStorage) + repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, v3) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, repositorySecrets, factory, renderingService, resourceClient, eventualRestConfigProvider) - v2 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) - apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v2) + v4 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) + apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v4) if err != nil { return nil, err } @@ -1126,8 +1133,9 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider) + mtDatasourceClientBuilder := mtdsclient.NewNullMTDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, mtDatasourceClientBuilder) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, mtDatasourceClientBuilder) repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider) if err != nil { @@ -1234,7 +1242,13 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { scopedPluginDatasourceProvider := datasource.ProvideDefaultPluginConfigs(service15, cacheServiceImpl, plugincontextProvider) v := builder.ProvideDefaultBuildHandlerChainFuncFromBuilders() aggregatorRunner := aggregatorrunner.ProvideNoopAggregatorConfigurator() - apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner) + playlistAppInstaller, err := playlist.RegisterAppInstaller(playlistService, cfg, featureToggles) + if err != nil { + return nil, err + } + v2 := appregistry.ProvideAppInstallers(playlistAppInstaller) + builderMetrics := builder.ProvideBuilderMetrics(registerer) + apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { return nil, err } @@ -1247,12 +1261,11 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService) - playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles) investigationsAppProvider := investigations.RegisterApp(cfg) checkregistryService := checkregistry.ProvideService(service15, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, noop, provisionedpluginsNoop, ssosettingsimplService, cfg, pluginerrsStore) advisorAppProvider := advisor2.RegisterApp(checkregistryService, cfg) alertingNotificationsAppProvider := notifications2.RegisterApp(cfg, alertNG) - appregistryService, err := appregistry.ProvideRegistryServiceSink(apiserverService, eventualRestConfigProvider, featureToggles, playlistAppProvider, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg) + appregistryService, err := appregistry.ProvideBuilderRunners(apiserverService, eventualRestConfigProvider, featureToggles, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg) if err != nil { return nil, err } @@ -1285,7 +1298,7 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } legacyDataSourceLookup := service7.ProvideLegacyDataSourceLookup(service15) - queryAPIBuilder, err := query2.RegisterAPIService(featureToggles, apiserverService, service15, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup) + queryAPIBuilder, err := query2.RegisterAPIService(cfg, featureToggles, apiserverService, service15, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup, exprService) if err != nil { return nil, err } @@ -1327,17 +1340,17 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { } secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) secureValueValidator := validator3.ProvideSecureValueValidator() - secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator) + secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator, accessClient) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer) if err != nil { return nil, err } - decryptService := decrypt.ProvideDecryptService(decryptStorage) - repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, decryptService) + v3 := decrypt.ProvideDecryptService(decryptStorage) + repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, v3) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, repositorySecrets, factory, renderingService, resourceClient, eventualRestConfigProvider) - v2 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) - apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v2) + v4 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) + apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v4) if err != nil { return nil, err } @@ -1521,7 +1534,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, encryption.ProvideDataKeyStorage, encryption.ProvideEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, encryption.ProvideDataKeyStorage, encryption.ProvideEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/services/apiserver/appinstaller/README.md b/pkg/services/apiserver/appinstaller/README.md new file mode 100644 index 00000000000..5451c26fa4b --- /dev/null +++ b/pkg/services/apiserver/appinstaller/README.md @@ -0,0 +1,80 @@ +# AppInstaller + +An `AppInstaller` (`appsdkapiserver.AppInstaller`) is a new concept introduced in v0.40.0 of the Grafana App SDK. It is an interface responsible for installing an app into the Grafana API server. Each app provides an `AppInstaller` implementation, which is then used by the API server to manage the app. + +## Architectural Changes + +### New App Installer Framework + +A new framework for installing and managing apps has been introduced in `pkg/services/apiserver/appinstaller`. This framework is responsible for: + +- **Schema Registration:** Registering the Kubernetes-style API schemas defined by an app. +- **Admission Plugins:** Registering admission plugins for apps. +- **OpenAPI Definitions:** Aggregating OpenAPI definitions from all installed apps. +- **API Installation:** Installing the API groups and resources defined by an app. +- **Lifecycle Management:** Managing the lifecycle of an app, including initialization and startup. + +### API Server Integration + +Grafana's core API server (`pkg/services/apiserver/service.go`) has been significantly updated to integrate with the new app installer framework. During startup, the API server now performs the following steps: + +1. Collects all `AppInstaller` instances provided by the apps. +2. Uses the `appinstaller` framework to register schemas, admission plugins, and OpenAPI definitions. +3. Installs the APIs for each app. +4. Initializes and starts each app. + +This ensures that apps are seamlessly integrated into Grafana's API server and that their resources are available through the Grafana API. + +### App Registration + +The method for registering apps has been updated: + +- The old way of registering apps using `ProvideBuilderRunners` in `pkg/registry/apps/apps.go` is now deprecated. +- The new, preferred way is to use the `ProvideAppInstallers` function, which returns a list of `AppInstaller` instances. + +This change streamlines the app registration process and makes it more consistent. + +## Migration Guide + +To migrate an existing app to the new SDK, you need to: + +1. Implement the `appsdkapiserver.AppInstaller` interface for your app. +2. Update your app's registration to use the `ProvideAppInstallers` function. + +### Example: Playlist App + +The Playlist app has been migrated to the new App SDK. Let's look at how it was done. + +Previously, the Playlist app was registered using `ProvideBuilderRunners`: + +```go +// pkg/registry/apps/apps.go (before) +func ProvideBuilderRunners( + // ... + playlistAppProvider *playlist.PlaylistAppProvider, + // ... +) (*Service, error) { + // ... + providers := []app.Provider{playlistAppProvider} + // ... +} +``` + +Now, the Playlist app provides an `AppInstaller` and is registered through `ProvideAppInstallers`: + +```go +// pkg/registry/apps/apps.go (after) +func ProvideAppInstallers( + playlistAppInstaller *playlist.PlaylistAppInstaller, +) []appsdkapiserver.AppInstaller { + return []appsdkapiserver.AppInstaller{playlistAppInstaller} +} +``` + +The implementation of the `PlaylistAppInstaller` can be found in `pkg/registry/apps/playlist/register.go`. This file is a good reference for how to implement an `AppInstaller` for your own app. + +The `pkg/registry/apps/playlist/register.go` file does the following: +- It defines `PlaylistAppInstaller`, which embeds `appsdkapiserver.AppInstaller`. +- It implements `appinstaller.LegacyStorageProvider` to bridge with Grafana's existing playlist service. +- The `RegisterAppInstaller` function initializes the installer, providing the app's manifest, specific configuration, and associating Go types with API kinds. +- It shows how to provide a custom table converter for `kubectl get` style output. \ No newline at end of file diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go new file mode 100644 index 00000000000..d80e865b5f0 --- /dev/null +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -0,0 +1,199 @@ +package appinstaller + +import ( + "context" + "fmt" + "maps" + "time" + + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/logging" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/registry/generic" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/kube-openapi/pkg/common" +) + +type LegacyStorageGetterFunc func(schema.GroupVersionResource) grafanarest.Storage + +type LegacyStorageProvider interface { + GetLegacyStorage(schema.GroupVersionResource) grafanarest.Storage +} + +type AuthorizerProvider interface { + GetAuthorizer() authorizer.Authorizer +} + +type APIEnablementProvider interface { + // Do not implement this unless you have special circumstances! This is a list of resources that are allowed to be accessed in v0alpha1, + // to prevent accidental exposure of experimental APIs. While developing, use the feature flag `grafanaAPIServerWithExperimentalAPIs`. + // And then, when you're ready to expose this to the end user, go to v1beta1 instead. + GetAllowedV0Alpha1Resources() []string +} + +type AppInstallerConfig struct { + CustomConfig any + AllowedV0Alpha1Resources []string +} + +// serverLock interface defines a lock mechanism for executing actions with a timeout +type serverLock interface { + LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error +} + +// AddToScheme adds app installer schemas to the runtime scheme +func AddToScheme( + appInstallers []appsdkapiserver.AppInstaller, + scheme *runtime.Scheme, +) ([]schema.GroupVersion, error) { + var additionalGroupVersions []schema.GroupVersion + for _, installer := range appInstallers { + if err := installer.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("failed to add app installer scheme: %w", err) + } + additionalGroupVersions = append(additionalGroupVersions, installer.GroupVersions()...) + } + return additionalGroupVersions, nil +} + +// RegisterAdmissionPlugins registers admission plugins for app installers +func RegisterAdmissionPlugins( + ctx context.Context, + appInstallers []appsdkapiserver.AppInstaller, + options *grafanaapiserveroptions.Options, +) error { + logger := logging.FromContext(ctx) + + for _, installer := range appInstallers { + plugin := installer.AdmissionPlugin() + if plugin != nil { + md := installer.ManifestData() + if md == nil { + return fmt.Errorf("manifest is not initialized for installer for GroupVersions %v", installer.GroupVersions()) + } + pluginName := md.AppName + " admission" + options.RecommendedOptions.Admission.Plugins.Register(pluginName, plugin) + logger.Info("Registered admission plugin", "app", md.AppName) + } + } + return nil +} + +type AuthorizerRegistrar interface { + Register(gv schema.GroupVersion, authorizer authorizer.Authorizer) +} + +func RegisterAuthorizers( + ctx context.Context, + appInstallers []appsdkapiserver.AppInstaller, + registrar AuthorizerRegistrar, +) { + logger := logging.FromContext(ctx) + for _, installer := range appInstallers { + if authorizerProvider, ok := installer.(AuthorizerProvider); ok { + authorizer := authorizerProvider.GetAuthorizer() + for _, gv := range installer.GroupVersions() { + registrar.Register(gv, authorizer) + logger.Debug("Registered authorizer", "group", gv.Group, "version", gv.Version, "app") + } + } + } +} + +func BuildOpenAPIDefGetter( + appInstallers []appsdkapiserver.AppInstaller, +) func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + defs := make(map[string]common.OpenAPIDefinition) + maps.Copy(defs, appsdkapiserver.GetCommonOpenAPIDefinitions(ref)) + for _, installer := range appInstallers { + maps.Copy(defs, installer.GetOpenAPIDefinitions(ref)) + } + return defs + } +} + +func InstallAPIs( + ctx context.Context, + appInstallers []appsdkapiserver.AppInstaller, + server *genericapiserver.GenericAPIServer, + restOpsGetter generic.RESTOptionsGetter, + storageOpts *grafanaapiserveroptions.StorageOptions, + kvStore grafanarest.NamespacedKVStore, + lock serverLock, + namespaceMapper request.NamespaceMapper, + dualWriteService dualwrite.Service, + dualWriterMetrics *grafanarest.DualWriterMetrics, + builderMetrics *builder.BuilderMetrics, +) error { + logger := logging.FromContext(ctx) + + for _, installer := range appInstallers { + logger.Debug("Installing APIs for app installer", "app", installer.ManifestData().AppName) + wrapper := &serverWrapper{ + ctx: ctx, + GenericAPIServer: server, + installer: installer, + storageOpts: storageOpts, + restOptionsGetter: restOpsGetter, + kvStore: kvStore, + lock: lock, + namespaceMapper: namespaceMapper, + dualWriteService: dualWriteService, + dualWriterMetrics: dualWriterMetrics, + builderMetrics: builderMetrics, + } + if err := installer.InstallAPIs(wrapper, restOpsGetter); err != nil { + return fmt.Errorf("failed to install APIs for app %s: %w", installer.ManifestData().AppName, err) + } + logger.Info("Installed APIs for app", "app", installer.ManifestData().AppName) + } + return nil +} + +// RegisterPostStartHooks registers individual post start hooks for each app installer +func RegisterPostStartHooks( + appInstallers []appsdkapiserver.AppInstaller, + serverConfig *genericapiserver.RecommendedConfig, +) error { + for _, installer := range appInstallers { + md := installer.ManifestData() + if md == nil { + return fmt.Errorf("app installer has nil manifest data: %T", installer) + } + hook := createPostStartHook(installer) + if err := serverConfig.AddPostStartHook(md.AppName, hook); err != nil { + return fmt.Errorf("failed to register post start hook for app %s: %w", md.AppName, err) + } + } + return nil +} + +func createPostStartHook( + installer appsdkapiserver.AppInstaller, +) genericapiserver.PostStartHookFunc { + return func(hookContext genericapiserver.PostStartHookContext) error { + logger := logging.FromContext(hookContext.Context) + logger.Debug("Initializing app", "app", installer.ManifestData().AppName) + + if err := installer.InitializeApp(*hookContext.LoopbackClientConfig); err != nil { + logger.Error("Failed to initialize app", "app", installer.ManifestData().AppName, "error", err) + return fmt.Errorf("failed to initialize app %s: %w", installer.ManifestData().AppName, err) + } + + logger.Info("App initialized", "app", installer.ManifestData().AppName) + app, err := installer.App() + if err != nil { + logger.Error("Failed to initialize app", "app", installer.ManifestData().AppName, "error", err) + return fmt.Errorf("failed to get app from installer %s: %w", installer.ManifestData().AppName, err) + } + return app.Runner().Run(hookContext.Context) + } +} diff --git a/pkg/services/apiserver/appinstaller/installer_test.go b/pkg/services/apiserver/appinstaller/installer_test.go new file mode 100644 index 00000000000..89c4bfa0164 --- /dev/null +++ b/pkg/services/apiserver/appinstaller/installer_test.go @@ -0,0 +1,143 @@ +package appinstaller + +import ( + "context" + "testing" + + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func TestRegisterAuthorizers(t *testing.T) { + tests := []struct { + name string + appInstallers []appsdkapiserver.AppInstaller + expectedRegisters int + }{ + { + name: "empty installers list", + appInstallers: []appsdkapiserver.AppInstaller{}, + expectedRegisters: 0, + }, + { + name: "installer without authorizer provider", + appInstallers: []appsdkapiserver.AppInstaller{ + &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "test.example.com", Version: "v1"}, + }, + }, + }, + expectedRegisters: 0, + }, + { + name: "single installer with authorizer provider", + appInstallers: []appsdkapiserver.AppInstaller{ + &mockAppInstallerWithAuth{ + mockAppInstaller: &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "test.example.com", Version: "v1"}, + }, + }, + mockAuthorizer: &mockAuthorizer{}, + }, + }, + expectedRegisters: 1, + }, + { + name: "installer with multiple group versions", + appInstallers: []appsdkapiserver.AppInstaller{ + &mockAppInstallerWithAuth{ + mockAppInstaller: &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "test.example.com", Version: "v1"}, + {Group: "test.example.com", Version: "v2"}, + {Group: "other.example.com", Version: "v1"}, + }, + }, + mockAuthorizer: &mockAuthorizer{}, + }, + }, + expectedRegisters: 3, + }, + { + name: "multiple installers with mixed authorizer support", + appInstallers: []appsdkapiserver.AppInstaller{ + &mockAppInstallerWithAuth{ + mockAppInstaller: &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "test.example.com", Version: "v1"}, + }, + }, + mockAuthorizer: &mockAuthorizer{}, + }, + &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "other.example.com", Version: "v1"}, + }, + }, + &mockAppInstallerWithAuth{ + mockAppInstaller: &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "another.example.com", Version: "v1"}, + {Group: "another.example.com", Version: "v2"}, + }, + }, + mockAuthorizer: &mockAuthorizer{}, + }, + }, + expectedRegisters: 3, // 1 from first installer + 2 from third installer + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + registrar := &mockAuthorizerRegistrar{} + RegisterAuthorizers(ctx, tt.appInstallers, registrar) + require.Equal(t, tt.expectedRegisters, len(registrar.registrations)) + }) + } +} + +type mockAppInstaller struct { + appsdkapiserver.AppInstaller // Embed the interface + groupVersions []schema.GroupVersion +} + +func (m *mockAppInstaller) GroupVersions() []schema.GroupVersion { + return m.groupVersions +} + +type mockAppInstallerWithAuth struct { + *mockAppInstaller + mockAuthorizer authorizer.Authorizer +} + +func (m *mockAppInstallerWithAuth) GetAuthorizer() authorizer.Authorizer { + return m.mockAuthorizer +} + +type mockRegistration struct { + groupVersion schema.GroupVersion + authorizer authorizer.Authorizer +} + +type mockAuthorizerRegistrar struct { + registrations []mockRegistration +} + +func (m *mockAuthorizerRegistrar) Register(gv schema.GroupVersion, auth authorizer.Authorizer) { + m.registrations = append(m.registrations, mockRegistration{ + groupVersion: gv, + authorizer: auth, + }) +} + +type mockAuthorizer struct{} + +func (m *mockAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { + return authorizer.DecisionAllow, "test", nil +} diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go new file mode 100644 index 00000000000..723c0753605 --- /dev/null +++ b/pkg/services/apiserver/appinstaller/server.go @@ -0,0 +1,95 @@ +package appinstaller + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + genericapiserver "k8s.io/apiserver/pkg/server" + + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/logging" + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" +) + +var _ appsdkapiserver.GenericAPIServer = (*serverWrapper)(nil) + +type serverWrapper struct { + ctx context.Context + appsdkapiserver.GenericAPIServer + installer appsdkapiserver.AppInstaller + restOptionsGetter generic.RESTOptionsGetter + storageOpts *grafanaapiserveroptions.StorageOptions + kvStore grafanarest.NamespacedKVStore + lock serverLock + namespaceMapper request.NamespaceMapper + dualWriteService dualwrite.Service + dualWriterMetrics *grafanarest.DualWriterMetrics + builderMetrics *builder.BuilderMetrics +} + +func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupInfo) error { + log := logging.FromContext(s.ctx) + legacyProvider, ok := s.installer.(LegacyStorageProvider) + if !ok { + return s.GenericAPIServer.InstallAPIGroup(apiGroupInfo) + } + for v, storageMap := range apiGroupInfo.VersionedResourcesStorageMap { + for storagePath, restStorage := range storageMap { + genericStorage, ok := restStorage.(*genericregistry.Store) + if !ok { + log.Error("Expected generic registry store", "storagePath", storagePath, "version", v) + continue + } + resource, err := getResourceFromStoragePath(storagePath) + if err != nil { + return err + } + gr := schema.GroupResource{ + Group: s.installer.ManifestData().Group, + Resource: resource, + } + genericStorage.KeyRootFunc = grafanaregistry.KeyRootFunc(gr) + genericStorage.KeyFunc = grafanaregistry.NamespaceKeyFunc(gr) + genericStorage.UpdateStrategy = &updateStrategyWrapper{ + RESTUpdateStrategy: genericStorage.UpdateStrategy, + } + + dw, err := NewDualWriter( + s.ctx, + gr, + s.storageOpts, + legacyProvider.GetLegacyStorage(gr.WithVersion(v)), + grafanarest.Storage(genericStorage), + s.kvStore, + s.lock, + s.namespaceMapper, + s.dualWriteService, + s.dualWriterMetrics, + s.builderMetrics, + ) + if err != nil { + return err + } + apiGroupInfo.VersionedResourcesStorageMap[v][storagePath] = dw + } + } + + return s.GenericAPIServer.InstallAPIGroup(apiGroupInfo) +} + +func getResourceFromStoragePath(storagePath string) (string, error) { + parts := strings.Split(storagePath, "/") + if len(parts) < 1 { + return "", fmt.Errorf("invalid storage path: %s", storagePath) + } + return parts[0], nil +} diff --git a/pkg/services/apiserver/appinstaller/storage.go b/pkg/services/apiserver/appinstaller/storage.go new file mode 100644 index 00000000000..83610ea81bb --- /dev/null +++ b/pkg/services/apiserver/appinstaller/storage.go @@ -0,0 +1,119 @@ +package appinstaller + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + k8srequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/klog/v2" + + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/apiserver/options" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" +) + +// NewDualWriter creates a dual writer for the given group resource using the provided configuration +func NewDualWriter( + _ context.Context, + gr schema.GroupResource, + storageOpts *options.StorageOptions, + legacy grafanarest.Storage, + storage grafanarest.Storage, + kvStore grafanarest.NamespacedKVStore, + lock serverLock, + namespaceMapper request.NamespaceMapper, + dualWriteService dualwrite.Service, + dualWriterMetrics *grafanarest.DualWriterMetrics, + builderMetrics *builder.BuilderMetrics, +) (grafanarest.Storage, error) { + // Dashboards + Folders may be managed (depends on feature toggles and database state) + if dualWriteService != nil && dualWriteService.ShouldManage(gr) { + return dualWriteService.NewStorage(gr, legacy, storage) // eventually this can replace this whole function + } + + key := gr.String() // ${resource}.{group} eg playlists.playlist.grafana.app + + // Get the option from custom.ini/command line + // when missing this will default to mode zero (legacy only) + var mode = grafanarest.DualWriterMode(0) + + var ( + dualWriterPeriodicDataSyncJobEnabled bool + dualWriterMigrationDataSyncDisabled bool + dataSyncerInterval = time.Hour + dataSyncerRecordsLimit = 1000 + ) + + resourceConfig, resourceExists := storageOpts.UnifiedStorageConfig[key] + if resourceExists { + mode = resourceConfig.DualWriterMode + dualWriterPeriodicDataSyncJobEnabled = resourceConfig.DualWriterPeriodicDataSyncJobEnabled + dualWriterMigrationDataSyncDisabled = resourceConfig.DualWriterMigrationDataSyncDisabled + dataSyncerInterval = resourceConfig.DataSyncerInterval + dataSyncerRecordsLimit = resourceConfig.DataSyncerRecordsLimit + } + + // Force using storage only -- regardless of internal synchronization state + if mode == grafanarest.Mode5 { + return storage, nil + } + + // Moving from one version to the next can only happen after the previous step has + // successfully synchronized. + requestInfo := getRequestInfo(gr, namespaceMapper) + + syncerCfg := &grafanarest.SyncerConfig{ + Kind: key, + RequestInfo: requestInfo, + Mode: mode, + SkipDataSync: dualWriterMigrationDataSyncDisabled, + LegacyStorage: legacy, + Storage: storage, + ServerLockService: lock, + DataSyncerInterval: dataSyncerInterval, + DataSyncerRecordsLimit: dataSyncerRecordsLimit, + } + + ctx := context.Background() + // This also sets the currentMode on the syncer config. + currentMode, err := grafanarest.SetDualWritingMode(ctx, kvStore, syncerCfg, dualWriterMetrics) + if err != nil { + return nil, err + } + + builderMetrics.RecordDualWriterModes(gr.Resource, gr.Group, mode, currentMode) + + switch currentMode { + case grafanarest.Mode0: + return legacy, nil + case grafanarest.Mode4, grafanarest.Mode5: + return storage, nil + default: + } + + if dualWriterPeriodicDataSyncJobEnabled { + // The mode might have changed in SetDualWritingMode, so apply current mode first. + syncerCfg.Mode = currentMode + if err := grafanarest.StartPeriodicDataSyncer(ctx, syncerCfg, dualWriterMetrics); err != nil { + return nil, err + } + } + + // when unable to use + if currentMode != mode { + klog.Warningf("Requested DualWrite mode: %d, but using %d for %+v", mode, currentMode, gr) + } + return dualwrite.NewDualWriter(gr, currentMode, legacy, storage) +} + +func getRequestInfo(gr schema.GroupResource, namespaceMapper request.NamespaceMapper) *k8srequest.RequestInfo { + return &k8srequest.RequestInfo{ + APIGroup: gr.Group, + Resource: gr.Resource, + Name: "", + Namespace: namespaceMapper(int64(1)), + } +} diff --git a/pkg/services/apiserver/appinstaller/strategy.go b/pkg/services/apiserver/appinstaller/strategy.go new file mode 100644 index 00000000000..cc30a21bc89 --- /dev/null +++ b/pkg/services/apiserver/appinstaller/strategy.go @@ -0,0 +1,19 @@ +package appinstaller + +import ( + genericrest "k8s.io/apiserver/pkg/registry/rest" +) + +type updateStrategyWrapper struct { + genericrest.RESTUpdateStrategy +} + +func (s *updateStrategyWrapper) AllowCreateOnUpdate() bool { + // needed for dual write to work correctly + return true +} + +func (s *updateStrategyWrapper) AllowUnconditionalUpdate() bool { + // needed for dual write to work correctly + return true +} diff --git a/pkg/services/apiserver/auth/authorizer/authorizer.go b/pkg/services/apiserver/auth/authorizer/authorizer.go index b41849df347..54ea8c081d7 100644 --- a/pkg/services/apiserver/auth/authorizer/authorizer.go +++ b/pkg/services/apiserver/auth/authorizer/authorizer.go @@ -18,7 +18,8 @@ type GrafanaAuthorizer struct { auth authorizer.Authorizer } -// NewGrafanaAuthorizer returns an authorizer configured for a grafana instance. +// NewGrafanaBuiltInSTAuthorizer returns an authorizer configured for a grafana instance. +// should not be used anywhere except for ST builtin Grafana // This authorizer is a chain of smaller authorizers that together form the decision if // access should be granted. // 1. We deny all impersonate request. @@ -28,7 +29,7 @@ type GrafanaAuthorizer struct { // 4. We check authorizer that is configured speficially for an api. // 5. As a last fallback we check Role, this will only happen if an api have not configured // an authorizer or return authorizer.DecisionNoOpinion -func NewGrafanaAuthorizer(cfg *setting.Cfg) *GrafanaAuthorizer { +func NewGrafanaBuiltInSTAuthorizer(cfg *setting.Cfg) *GrafanaAuthorizer { authorizers := []authorizer.Authorizer{ newImpersonationAuthorizer(), authorizerfactory.NewPrivilegedGroups(k8suser.SystemPrivilegedGroup), diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 6cbd65b0d9f..eb29c6b4399 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -107,9 +107,11 @@ func SetupConfig( buildCommit string, buildBranch string, buildHandlerChainFuncFromBuilders BuildHandlerChainFuncFromBuilders, + gvs []schema.GroupVersion, + additionalOpenAPIDefGetters []common.GetOpenAPIDefinitions, ) error { serverConfig.AdmissionControl = NewAdmissionFromBuilders(builders) - defsGetter := GetOpenAPIDefinitions(builders) + defsGetter := GetOpenAPIDefinitions(builders, additionalOpenAPIDefGetters...) serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig( openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter), openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme)) @@ -119,7 +121,7 @@ func SetupConfig( openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme)) // Add the custom routes to service discovery - serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders) + serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs) serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { meta := r.Metadata() kind := "" @@ -226,6 +228,7 @@ func SetupConfig( // Set the swagger build versions serverConfig.OpenAPIConfig.Info.Title = "Grafana API Server" serverConfig.OpenAPIConfig.Info.Version = buildVersion + serverConfig.OpenAPIV3Config.Info.Title = "Grafana API Server" serverConfig.OpenAPIV3Config.Info.Version = buildVersion serverConfig.SkipOpenAPIInstallation = false @@ -278,13 +281,13 @@ func InstallAPIs( dualWriteService dualwrite.Service, optsregister apistore.StorageOptionsRegister, features featuremgmt.FeatureToggles, + dualWriterMetrics *grafanarest.DualWriterMetrics, + builderMetrics *BuilderMetrics, ) error { // dual writing is only enabled when the storage type is not legacy. // this is needed to support setting a default RESTOptionsGetter for new APIs that don't // support the legacy storage type. var dualWrite grafanarest.DualWriteBuilder - metrics := newBuilderMetrics(reg) - dualWriterMetrics := grafanarest.NewDualWriterMetrics(reg) // nolint:staticcheck if storageOpts.StorageType != options.StorageTypeLegacy { @@ -346,7 +349,7 @@ func InstallAPIs( return nil, err } - metrics.recordDualWriterModes(gr.Resource, gr.Group, mode, currentMode) + builderMetrics.RecordDualWriterModes(gr.Resource, gr.Group, mode, currentMode) switch currentMode { case grafanarest.Mode0: diff --git a/pkg/services/apiserver/builder/metrics.go b/pkg/services/apiserver/builder/metrics.go index 663c24902c1..f3fc807e72b 100644 --- a/pkg/services/apiserver/builder/metrics.go +++ b/pkg/services/apiserver/builder/metrics.go @@ -6,13 +6,13 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) -type builderMetrics struct { +type BuilderMetrics struct { dualWriterTargetMode *prometheus.GaugeVec dualWriterCurrentMode *prometheus.GaugeVec } -func newBuilderMetrics(reg prometheus.Registerer) *builderMetrics { - return &builderMetrics{ +func ProvideBuilderMetrics(reg prometheus.Registerer) *BuilderMetrics { + return &BuilderMetrics{ dualWriterTargetMode: promauto.With(reg).NewGaugeVec(prometheus.GaugeOpts{ Name: "unified_storage_dual_writer_target_mode", Help: "Unified Storage dual writer target mode", @@ -24,7 +24,11 @@ func newBuilderMetrics(reg prometheus.Registerer) *builderMetrics { } } -func (m *builderMetrics) recordDualWriterModes(resource, group string, targetMode, currentMode grafanarest.DualWriterMode) { +func (m *BuilderMetrics) RecordDualWriterModes(resource, group string, targetMode, currentMode grafanarest.DualWriterMode) { m.dualWriterTargetMode.WithLabelValues(resource, group).Set(float64(targetMode)) m.dualWriterCurrentMode.WithLabelValues(resource, group).Set(float64(currentMode)) } + +func ProvideDualWriterMetrics(reg prometheus.Registerer) *grafanarest.DualWriterMetrics { + return grafanarest.NewDualWriterMetrics(reg) +} diff --git a/pkg/services/apiserver/builder/openapi.go b/pkg/services/apiserver/builder/openapi.go index cddbb1e4b15..9489ac38b95 100644 --- a/pkg/services/apiserver/builder/openapi.go +++ b/pkg/services/apiserver/builder/openapi.go @@ -4,6 +4,7 @@ import ( "maps" "strings" + "k8s.io/apimachinery/pkg/runtime/schema" openapi "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" spec "k8s.io/kube-openapi/pkg/validation/spec" @@ -14,11 +15,18 @@ import ( ) // This should eventually live in grafana-app-sdk -func GetOpenAPIDefinitions(builders []APIGroupBuilder) openapi.GetOpenAPIDefinitions { +func GetOpenAPIDefinitions(builders []APIGroupBuilder, additionalGetters ...openapi.GetOpenAPIDefinitions) openapi.GetOpenAPIDefinitions { return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition { defs := common.GetOpenAPIDefinitions(ref) // common grafana apis maps.Copy(defs, data.GetOpenAPIDefinitions(ref)) maps.Copy(defs, secret.GetOpenAPIDefinitions(ref)) // Expose secret reference to all resources + + for _, getter := range additionalGetters { + if getter != nil { + maps.Copy(defs, getter(ref)) + } + } + // TODO: add timerange to upstream SDK setup maps.Copy(defs, map[string]openapi.OpenAPIDefinition{ "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.TimeRange": { @@ -41,157 +49,165 @@ func GetOpenAPIDefinitions(builders []APIGroupBuilder) openapi.GetOpenAPIDefinit } } +func addBuilderRoutes( + targetGroupVersion schema.GroupVersion, + openAPISpec *spec3.OpenAPI, + apiGroupBuilders []APIGroupBuilder, +) (*spec3.OpenAPI, error) { + for _, apiGroupBuilder := range apiGroupBuilders { + // Optionally include raw http handlers for all builders + for _, gv := range GetGroupVersions(apiGroupBuilder) { + if gv != targetGroupVersion { + continue // Only add routes for the target group version + } + provider, ok := apiGroupBuilder.(APIGroupRouteProvider) + if ok && provider != nil { + routes := provider.GetAPIRoutes(gv) + if routes != nil { + for _, route := range routes.Root { + openAPISpec.Paths.Paths["/apis/"+gv.String()+"/"+route.Path] = &spec3.Path{ + PathProps: *route.Spec, + } + } + + for _, route := range routes.Namespace { + openAPISpec.Paths.Paths["/apis/"+gv.String()+"/namespaces/{namespace}/"+route.Path] = &spec3.Path{ + PathProps: *route.Spec, + } + } + } + } + // Support direct manipulation of API results + processor, ok := apiGroupBuilder.(OpenAPIPostProcessor) + if ok { + return processor.PostProcessOpenAPI(openAPISpec) + } + } + } + return openAPISpec, nil +} + // Modify the OpenAPI spec to include the additional routes. -// Currently this requires: https://github.com/kubernetes/kube-openapi/pull/420 -// In future k8s release, the hook will use Config3 rather than the same hook for both v2 and v3 // nolint:gocyclo -func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { +func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) { if s.Paths == nil { return s, nil } - for _, b := range builders { - for _, gv := range GetGroupVersions(b) { - prefix := "/apis/" + gv.String() + "/" - if s.Paths.Paths[prefix] != nil { - copy := spec3.OpenAPI{ - Version: s.Version, - Info: &spec.Info{ - InfoProps: spec.InfoProps{ - Title: gv.String(), - Version: version, - }, + for _, gv := range gvs { + prefix := "/apis/" + gv.String() + "/" + if s.Paths.Paths[prefix] != nil { + copy := spec3.OpenAPI{ + Version: s.Version, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: gv.String(), + Version: version, }, - Components: s.Components, - ExternalDocs: s.ExternalDocs, - Servers: s.Servers, - Paths: s.Paths, - } - - for k, v := range copy.Paths.Paths { - if k == prefix { - continue // API discovery - } - - // Remove the deprecated watch URL -- can use list with ?watch=true - if strings.HasPrefix(k, prefix+"watch/") { - delete(copy.Paths.Paths, k) - continue - } - - // Remove the "for all namespaces" global routes from OpenAPI (v3) - if !strings.HasPrefix(k, prefix+"namespaces/") { - delete(copy.Paths.Paths, k) - continue - } - - // Delete has all parameters in the query string already - if v.Delete != nil { - action, ok := v.Delete.Extensions.GetString("x-kubernetes-action") - if ok && (action == "deletecollection" || action == "delete") { - v.Delete.RequestBody = nil // duplicates all the parameters - } - } - - // Replace any */* media types with json+yaml (protobuf?) - ops := []*spec3.Operation{v.Delete, v.Put, v.Post} - for _, op := range ops { - if op == nil || op.RequestBody == nil || len(op.RequestBody.Content) != 1 { - continue - } - content, ok := op.RequestBody.Content["*/*"] - if ok { - op.RequestBody.Content = map[string]*spec3.MediaType{ - "application/json": content, - "application/yaml": content, - "application/vnd.kubernetes.protobuf": content, - } - } - } - } - - sub := copy.Paths.Paths[prefix] - if sub != nil && sub.Get != nil { - sub.Get.Tags = []string{"API Discovery"} - sub.Get.Description = "Describe the available kubernetes resources" - } - - // Remove the growing list of kinds - for k, v := range copy.Components.Schemas { - if v.Extensions == nil { - continue - } - if strings.HasPrefix(k, "io.k8s.apimachinery.pkg.apis.meta.v1") { - delete(v.Extensions, "x-kubernetes-group-version-kind") // a growing list of everything - continue - } - - // Remove the internal annotations - val, ok := v.Extensions["x-kubernetes-group-version-kind"] - if ok { - gvks, ok := val.([]any) - if ok { - keep := make([]map[string]any, 0, len(gvks)) - for _, val := range gvks { - gvk, ok := val.(map[string]any) - if ok && gvk["version"] == "__internal" { - continue - } - keep = append(keep, gvk) - } - v.Extensions["x-kubernetes-group-version-kind"] = keep - } - } - } - - // Optionally include raw http handlers - provider, ok := b.(APIGroupRouteProvider) - if ok && provider != nil { - routes := provider.GetAPIRoutes(gv) - if routes != nil { - for _, route := range routes.Root { - copy.Paths.Paths[prefix+route.Path] = &spec3.Path{ - PathProps: *route.Spec, - } - } - - for _, route := range routes.Namespace { - copy.Paths.Paths[prefix+"namespaces/{namespace}/"+route.Path] = &spec3.Path{ - PathProps: *route.Spec, - } - } - } - } - - // Make the sub-resources (connect) share the same tags as the main resource - for path, spec := range copy.Paths.Paths { - idx := strings.LastIndex(path, "{name}/") - if idx > 0 { - parent := copy.Paths.Paths[path[:idx+6]] - if parent != nil && parent.Get != nil { - for _, op := range GetPathOperations(spec) { - if op != nil && op.Extensions != nil { - action, ok := op.Extensions.GetString("x-kubernetes-action") - if ok && action == "connect" { - op.Tags = parent.Get.Tags - } - } - } - } - } - } - - // Support direct manipulation of API results - processor, ok := b.(OpenAPIPostProcessor) - if ok { - return processor.PostProcessOpenAPI(©) - } - return ©, nil + }, + Components: s.Components, + ExternalDocs: s.ExternalDocs, + Servers: s.Servers, + Paths: s.Paths, } + + for k, v := range copy.Paths.Paths { + if k == prefix { + continue // API discovery + } + + // Remove the deprecated watch URL -- can use list with ?watch=true + if strings.HasPrefix(k, prefix+"watch/") { + delete(copy.Paths.Paths, k) + continue + } + + // Remove the "for all namespaces" global routes from OpenAPI (v3) + if !strings.HasPrefix(k, prefix+"namespaces/") { + delete(copy.Paths.Paths, k) + continue + } + + // Delete has all parameters in the query string already + if v.Delete != nil { + action, ok := v.Delete.Extensions.GetString("x-kubernetes-action") + if ok && (action == "deletecollection" || action == "delete") { + v.Delete.RequestBody = nil // duplicates all the parameters + } + } + + // Replace any */* media types with json+yaml (protobuf?) + ops := []*spec3.Operation{v.Delete, v.Put, v.Post} + for _, op := range ops { + if op == nil || op.RequestBody == nil || len(op.RequestBody.Content) != 1 { + continue + } + content, ok := op.RequestBody.Content["*/*"] + if ok { + op.RequestBody.Content = map[string]*spec3.MediaType{ + "application/json": content, + "application/yaml": content, + "application/vnd.kubernetes.protobuf": content, + } + } + } + } + + sub := copy.Paths.Paths[prefix] + if sub != nil && sub.Get != nil { + sub.Get.Tags = []string{"API Discovery"} + sub.Get.Description = "Describe the available kubernetes resources" + } + + // Remove the growing list of kinds + for k, v := range copy.Components.Schemas { + if v.Extensions == nil { + continue + } + if strings.HasPrefix(k, "io.k8s.apimachinery.pkg.apis.meta.v1") { + delete(v.Extensions, "x-kubernetes-group-version-kind") // a growing list of everything + continue + } + + // Remove the internal annotations + val, ok := v.Extensions["x-kubernetes-group-version-kind"] + if ok { + gvks, ok := val.([]any) + if ok { + keep := make([]map[string]any, 0, len(gvks)) + for _, val := range gvks { + gvk, ok := val.(map[string]any) + if ok && gvk["version"] == "__internal" { + continue + } + keep = append(keep, gvk) + } + v.Extensions["x-kubernetes-group-version-kind"] = keep + } + } + } + + // Make the sub-resources (connect) share the same tags as the main resource + for path, spec := range copy.Paths.Paths { + idx := strings.LastIndex(path, "{name}/") + if idx > 0 { + parent := copy.Paths.Paths[path[:idx+6]] + if parent != nil && parent.Get != nil { + for _, op := range GetPathOperations(spec) { + if op != nil && op.Extensions != nil { + action, ok := op.Extensions.GetString("x-kubernetes-action") + if ok && action == "connect" { + op.Tags = parent.Get.Tags + } + } + } + } + } + } + return addBuilderRoutes(gv, ©, builders) } } - return s, nil } } diff --git a/pkg/services/apiserver/client/client.go b/pkg/services/apiserver/client/client.go index 8aa75c22416..b909c82252d 100644 --- a/pkg/services/apiserver/client/client.go +++ b/pkg/services/apiserver/client/client.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" @@ -46,9 +47,9 @@ type k8sHandler struct { } func NewK8sHandler(dual dualwrite.Service, namespacer request.NamespaceMapper, gvr schema.GroupVersionResource, - restConfig func(context.Context) (*rest.Config, error), dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient, sorter sort.Service) K8sHandler { + restConfig func(context.Context) (*rest.Config, error), dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient, sorter sort.Service, features featuremgmt.FeatureToggles) K8sHandler { legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter) - searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), gvr.GroupResource(), resourceClient, legacySearcher) + searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), gvr.GroupResource(), resourceClient, legacySearcher, features) return &k8sHandler{ namespacer: namespacer, diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 52c8ad568b7..58bf5ad4ac7 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -17,14 +17,17 @@ import ( "k8s.io/apiserver/pkg/util/notfoundhandler" clientrest "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "k8s.io/kube-openapi/pkg/common" "github.com/grafana/authlib/types" "github.com/grafana/dskit/services" + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-plugin-sdk-go/backend" dataplaneaggregator "github.com/grafana/grafana/pkg/aggregator/apiserver" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" grafanaresponsewriter "github.com/grafana/grafana/pkg/apiserver/endpoints/responsewriter" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" @@ -37,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/services/apiserver/aggregatorrunner" + "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" "github.com/grafana/grafana/pkg/services/apiserver/auth/authenticator" "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/builder" @@ -105,6 +109,9 @@ type service struct { buildHandlerChainFuncFromBuilders builder.BuildHandlerChainFuncFromBuilders aggregatorRunner aggregatorrunner.AggregatorRunner + appInstallers []appsdkapiserver.AppInstaller + builderMetrics *builder.BuilderMetrics + dualWriterMetrics *grafanarest.DualWriterMetrics } func ProvideService( @@ -126,6 +133,8 @@ func ProvideService( eventualRestConfigProvider *eventualRestConfigProvider, reg prometheus.Registerer, aggregatorRunner aggregatorrunner.AggregatorRunner, + appInstallers []appsdkapiserver.AppInstaller, + builderMetrics *builder.BuilderMetrics, ) (*service, error) { scheme := builder.ProvideScheme() codecs := builder.ProvideCodecFactory(scheme) @@ -138,7 +147,7 @@ func ProvideService( rr: rr, stopCh: make(chan struct{}), builders: []builder.APIGroupBuilder{}, - authorizer: authorizer.NewGrafanaAuthorizer(cfg), + authorizer: authorizer.NewGrafanaBuiltInSTAuthorizer(cfg), tracing: tracing, db: db, // For Unified storage metrics: reg, @@ -153,6 +162,9 @@ func ProvideService( restConfigProvider: restConfigProvider, buildHandlerChainFuncFromBuilders: buildHandlerChainFuncFromBuilders, aggregatorRunner: aggregatorRunner, + appInstallers: appInstallers, + builderMetrics: builderMetrics, + dualWriterMetrics: grafanarest.NewDualWriterMetrics(reg), } // This will be used when running as a dskit service service := services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer) @@ -244,7 +256,7 @@ func (s *service) start(ctx context.Context) error { builders := s.builders groupVersions := make([]schema.GroupVersion, 0, len(builders)) - // Install schemas + // Install schemas for existing builders for _, b := range builders { gvs := builder.GetGroupVersions(b) groupVersions = append(groupVersions, gvs...) @@ -266,14 +278,29 @@ func (s *service) start(ctx context.Context) error { } } + // Add schemas from app installers to the scheme before creating options + additionalGroupVersions, err := appinstaller.AddToScheme(s.appInstallers, s.scheme) + if err != nil { + return err + } + groupVersions = append(groupVersions, additionalGroupVersions...) + o := grafanaapiserveroptions.NewOptions(s.codecs.LegacyCodec(groupVersions...)) - err := applyGrafanaConfig(s.cfg, s.features, o) + + // Register admission plugins from app installers after options are created + if err := appinstaller.RegisterAdmissionPlugins(ctx, s.appInstallers, o); err != nil { + return err + } + + // Register authorizers from app installers + appinstaller.RegisterAuthorizers(ctx, s.appInstallers, s.authorizer) + + err = applyGrafanaConfig(s.cfg, s.features, o) if err != nil { return err } if errs := o.Validate(); len(errs) != 0 { - // TODO: handle multiple errors return errs[0] } @@ -281,6 +308,7 @@ func (s *service) start(ctx context.Context) error { if err := o.ApplyTo(serverConfig); err != nil { return err } + serverConfig.Authorization.Authorizer = s.authorizer serverConfig.Authentication.Authenticator = authenticator.NewAuthenticator(serverConfig.Authentication.Authenticator) serverConfig.TracerProvider = s.tracing.GetTracerProvider() @@ -303,12 +331,14 @@ func (s *service) start(ctx context.Context) error { } else { getter := apistore.NewRESTOptionsGetterForClient(s.unified, o.RecommendedOptions.Etcd.StorageConfig, s.restConfigProvider) optsregister = getter.RegisterOptions - - // Use unified storage client serverConfig.RESTOptionsGetter = getter } - // Add OpenAPI specs for each group+version + defGetters := []common.GetOpenAPIDefinitions{ + appinstaller.BuildOpenAPIDefGetter(s.appInstallers), + } + + // Add OpenAPI specs for each group+version (existing builders) err = builder.SetupConfig( s.scheme, serverConfig, @@ -318,6 +348,8 @@ func (s *service) start(ctx context.Context) error { s.cfg.BuildCommit, s.cfg.BuildBranch, s.buildHandlerChainFuncFromBuilders, + groupVersions, + defGetters, ) if err != nil { return err @@ -325,27 +357,48 @@ func (s *service) start(ctx context.Context) error { notFoundHandler := notfoundhandler.New(s.codecs, genericapifilters.NoMuxAndDiscoveryIncompleteKey) + if err := appinstaller.RegisterPostStartHooks(s.appInstallers, serverConfig); err != nil { + return fmt.Errorf("failed to register post start hooks for app installers: %w", err) + } + // Create the server server, err := serverConfig.Complete().New("grafana-apiserver", genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler)) if err != nil { return err } - // Install the API group+version + // Install the API group+version for existing builders err = builder.InstallAPIs(s.scheme, s.codecs, server, serverConfig.RESTOptionsGetter, builders, o.StorageOptions, - // Required for the dual writer initialization s.metrics, request.GetNamespaceMapper(s.cfg), - kvstore.WithNamespace(s.kvStore, 0, "storage.dualwriting"), // NOTE: will be removed and replaced with the dual writer utility + kvstore.WithNamespace(s.kvStore, 0, "storage.dualwriting"), s.serverLockService, s.storageStatus, optsregister, s.features, + s.dualWriterMetrics, + s.builderMetrics, ) if err != nil { return err } + if err := appinstaller.InstallAPIs( + ctx, + s.appInstallers, + server, + serverConfig.RESTOptionsGetter, + o.StorageOptions, + kvstore.WithNamespace(s.kvStore, 0, "storage.dualwriting"), + s.serverLockService, + request.GetNamespaceMapper(s.cfg), + s.storageStatus, + s.dualWriterMetrics, + s.builderMetrics, + ); err != nil { + return err + } + // stash the options for later use s.options = o diff --git a/pkg/services/apiserver/wireset.go b/pkg/services/apiserver/wireset.go index 98200b20ce5..7178d092f17 100644 --- a/pkg/services/apiserver/wireset.go +++ b/pkg/services/apiserver/wireset.go @@ -7,6 +7,8 @@ import ( ) var WireSet = wire.NewSet( + builder.ProvideDualWriterMetrics, + builder.ProvideBuilderMetrics, ProvideEventualRestConfigProvider, wire.Bind(new(RestConfigProvider), new(*eventualRestConfigProvider)), wire.Bind(new(DirectRestConfigProvider), new(*eventualRestConfigProvider)), diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index 69cbc6b8e08..a82b1434152 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strconv" + "sync/atomic" claims "github.com/grafana/authlib/types" "go.opentelemetry.io/otel/attribute" @@ -83,8 +84,8 @@ var ( // StaticSCIMConfig represents the static SCIM configuration from config.ini type StaticSCIMConfig struct { - AllowNonProvisionedUsers bool IsUserProvisioningEnabled bool + RejectNonProvisionedUsers bool } func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService, @@ -93,13 +94,13 @@ func ProvideUserSync(userService user.Service, userProtectionService login.UserP ) *UserSync { scimSection := cfg.Raw.Section("auth.scim") staticConfig := &StaticSCIMConfig{ - AllowNonProvisionedUsers: scimSection.Key("allow_non_provisioned_users").MustBool(false), IsUserProvisioningEnabled: scimSection.Key("user_sync_enabled").MustBool(false), + RejectNonProvisionedUsers: scimSection.Key("reject_non_provisioned_users").MustBool(false), } return &UserSync{ - allowNonProvisionedUsers: staticConfig.AllowNonProvisionedUsers, isUserProvisioningEnabled: staticConfig.IsUserProvisioningEnabled, + rejectNonProvisionedUsers: staticConfig.RejectNonProvisionedUsers, userService: userService, authInfoService: authInfoService, userProtectionService: userProtectionService, @@ -114,8 +115,8 @@ func ProvideUserSync(userService user.Service, userProtectionService login.UserP } type UserSync struct { - allowNonProvisionedUsers bool isUserProvisioningEnabled bool + rejectNonProvisionedUsers bool userService user.Service authInfoService login.AuthInfoService userProtectionService login.UserProtectionService @@ -126,6 +127,18 @@ type UserSync struct { lastSeenSF *singleflight.Group scimUtil *scimutil.SCIMUtil staticConfig *StaticSCIMConfig + scimSuccessfulLogin atomic.Bool +} + +// GetUsageStats implements registry.ProvidesUsageStats +func (s *UserSync) GetUsageStats(ctx context.Context) map[string]any { + stats := map[string]any{} + if s.scimSuccessfulLogin.Load() { + stats["stats.features.scim.has_successful_login.count"] = 1 + } else { + stats["stats.features.scim.has_successful_login.count"] = 0 + } + return stats } // ValidateUserProvisioningHook validates if a user should be allowed access based on provisioning status and configuration @@ -169,12 +182,18 @@ func (s *UserSync) ValidateUserProvisioningHook(ctx context.Context, currentIden return errUserExternalUIDMismatch.Errorf("the provisioned user.ExternalUID does not match the authinfo.ExternalUID") } log.Debug("User is provisioned, access granted") + s.scimSuccessfulLogin.Store(true) + return nil } - // Reject non-provisioned users - log.Error("Failed to access user, user is not provisioned") - return errUserNotProvisioned.Errorf("user is not provisioned") + // Reject non-provisioned users if configured to do so + if s.shouldRejectNonProvisionedUsers(ctx, currentIdentity) { + log.Error("Failed to authenticate user, user is not provisioned") + return errUserNotProvisioned.Errorf("user is not provisioned") + } + + return nil } func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdentity *authn.Identity) bool { @@ -182,12 +201,10 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti // Use dynamic SCIM settings if available, otherwise fall back to static config effectiveUserSyncEnabled := s.isUserProvisioningEnabled - effectiveAllowNonProvisionedUsers := s.allowNonProvisionedUsers if s.scimUtil != nil { orgID := currentIdentity.GetOrgID() effectiveUserSyncEnabled = s.scimUtil.IsUserSyncEnabled(ctx, orgID, s.staticConfig.IsUserProvisioningEnabled) - effectiveAllowNonProvisionedUsers = s.scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, s.staticConfig.AllowNonProvisionedUsers) } if !effectiveUserSyncEnabled { @@ -195,11 +212,6 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti return true } - if effectiveAllowNonProvisionedUsers { - log.Debug("Non-provisioned users are allowed, skipping validation") - return true - } - if currentIdentity.AuthenticatedBy == login.GrafanaComAuthModule { log.Debug("User is authenticated via GrafanaComAuthModule, skipping validation") return true @@ -208,6 +220,17 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti return false } +func (s *UserSync) shouldRejectNonProvisionedUsers(ctx context.Context, currentIdentity *authn.Identity) bool { + effectiveRejectNonProvisionedUsers := s.rejectNonProvisionedUsers + + if s.scimUtil != nil { + orgID := currentIdentity.GetOrgID() + effectiveRejectNonProvisionedUsers = s.scimUtil.AreNonProvisionedUsersRejected(ctx, orgID, s.staticConfig.RejectNonProvisionedUsers) + } + + return effectiveRejectNonProvisionedUsers +} + // SyncUserHook syncs a user with the database func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error { ctx, span := s.tracer.Start(ctx, "user.sync.SyncUserHook") diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index 73bb14d6bc4..49e50f55fdc 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -1092,11 +1092,24 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { }, }, { - desc: "it should skip validation if allowedNonProvisionedUsers is enabled", + desc: "it should skip validation if rejectNonProvisionedUsers is disabled", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = true + userSyncService.rejectNonProvisionedUsers = false userSyncService.isUserProvisioningEnabled = true + userSyncService.userService = &usertest.FakeUserService{ + ExpectedUser: &user.User{ + ID: 1, + IsProvisioned: false, + }, + } + userSyncService.authInfoService = &authinfotest.FakeService{ + ExpectedUserAuth: &login.UserAuth{ + UserId: 1, + AuthModule: login.GenericOAuthModule, + AuthId: "1", + }, + } return userSyncService }, identity: &authn.Identity{ @@ -1111,7 +1124,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should skip validation if the user is authenticated via GrafanaComAuthModule", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true return userSyncService }, @@ -1127,7 +1140,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should fail to validate the identity with the provisioned user, unexpected error", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedError: errors.New("random error"), @@ -1148,7 +1161,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should fail to validate the identity with the provisioned user, no user found", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{} return userSyncService @@ -1167,7 +1180,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should fail to validate the provisioned user.ExternalUID with the identity.ExternalUID - empty ExternalUID", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1198,7 +1211,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should fail to validate the provisioned user.ExternalUID with the identity.ExternalUID - different ExternalUID", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1230,7 +1243,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should successfully validate the provisioned user.ExternalUID with the identity.ExternalUID", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1259,10 +1272,10 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { expectedErr: nil, }, { - desc: "it should failed to validate a non provisioned user when retrieved from the database", + desc: "it should fail to validate a non provisioned user when configured to reject non provisioned users", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1290,6 +1303,38 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { }, expectedErr: errUserNotProvisioned.Errorf("user is not provisioned"), }, + { + desc: "it should skip to validate a non provisioned user when configured to allow non provisioned users", + userSyncServiceSetup: func() *UserSync { + userSyncService := initUserSyncService() + userSyncService.rejectNonProvisionedUsers = false + userSyncService.isUserProvisioningEnabled = true + userSyncService.userService = &usertest.FakeUserService{ + ExpectedUser: &user.User{ + ID: 1, + IsProvisioned: false, + }, + } + userSyncService.authInfoService = &authinfotest.FakeService{ + ExpectedUserAuth: &login.UserAuth{ + UserId: 1, + AuthModule: login.SAMLAuthModule, + AuthId: "1", + ExternalUID: "random-external-uid", + }, + } + return userSyncService + }, + identity: &authn.Identity{ + AuthenticatedBy: login.SAMLAuthModule, + AuthID: "1", + ExternalUID: "different-external-uid", + ClientParams: authn.ClientParams{ + SyncUser: true, + }, + }, + expectedErr: nil, + }, { desc: "ValidateProvisioning: DB ExternalUID is empty, Incoming ExternalUID is empty - expect mismatch (stricter logic)", userSyncServiceSetup: func() *UserSync { @@ -1351,7 +1396,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should skip ExternalUID validation for a SAML-provisioned user accessed by a non-SAML method with an empty incoming ExternalUID", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = false userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1380,7 +1425,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) { desc: "it should fail validation when a provisioned user is accessed by SAML with an empty incoming ExternalUID", userSyncServiceSetup: func() *UserSync { userSyncService := initUserSyncService() - userSyncService.allowNonProvisionedUsers = false + userSyncService.rejectNonProvisionedUsers = true userSyncService.isUserProvisioningEnabled = true userSyncService.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{ @@ -1425,10 +1470,10 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { // Mock SCIM utility for testing type mockSCIMUtil struct { - userSyncEnabled bool - nonProvisionedUsersAllowed bool - shouldUseDynamicConfig bool - shouldReturnError bool + userSyncEnabled bool + nonProvisionedUsersRejected bool + shouldUseDynamicConfig bool + shouldReturnError bool } createMockSCIMUtil := func(mockCfg *mockSCIMUtil) *scimutil.SCIMUtil { @@ -1453,9 +1498,9 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { "namespace": "default", }, "spec": map[string]interface{}{ - "enableUserSync": mockCfg.userSyncEnabled, - "enableGroupSync": false, // Not used for this test - "allowNonProvisionedUsers": mockCfg.nonProvisionedUsersAllowed, + "enableUserSync": mockCfg.userSyncEnabled, + "enableGroupSync": false, // Not used for this test + "rejectNonProvisionedUsers": mockCfg.nonProvisionedUsersRejected, }, }, } @@ -1467,13 +1512,13 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { } tests := []struct { - name string - identity *authn.Identity - staticConfig *StaticSCIMConfig - mockSCIMUtil *mockSCIMUtil - expectedUserSyncEnabled bool - expectedNonProvisionedAllowed bool - expectedError error + name string + identity *authn.Identity + staticConfig *StaticSCIMConfig + mockSCIMUtil *mockSCIMUtil + expectedUserSyncEnabled bool + expectedNonProvisionedRejected bool + expectedError error }{ { name: "SCIM util nil - uses static config", @@ -1483,11 +1528,11 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { }, staticConfig: &StaticSCIMConfig{ IsUserProvisioningEnabled: true, - AllowNonProvisionedUsers: false, + RejectNonProvisionedUsers: false, }, - mockSCIMUtil: nil, // No SCIM util - expectedUserSyncEnabled: true, - expectedNonProvisionedAllowed: false, + mockSCIMUtil: nil, // No SCIM util + expectedUserSyncEnabled: true, + expectedNonProvisionedRejected: false, }, { name: "SCIM util with dynamic config - user sync enabled", @@ -1497,15 +1542,15 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { }, staticConfig: &StaticSCIMConfig{ IsUserProvisioningEnabled: false, // Static disabled - AllowNonProvisionedUsers: false, + RejectNonProvisionedUsers: true, }, mockSCIMUtil: &mockSCIMUtil{ - userSyncEnabled: true, // Dynamic enabled - nonProvisionedUsersAllowed: true, - shouldUseDynamicConfig: true, + userSyncEnabled: true, // Dynamic enabled + nonProvisionedUsersRejected: true, + shouldUseDynamicConfig: true, }, - expectedUserSyncEnabled: true, - expectedNonProvisionedAllowed: true, + expectedUserSyncEnabled: true, + expectedNonProvisionedRejected: true, }, { name: "SCIM util with dynamic config - user sync disabled", @@ -1515,15 +1560,15 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { }, staticConfig: &StaticSCIMConfig{ IsUserProvisioningEnabled: true, // Static enabled - AllowNonProvisionedUsers: true, + RejectNonProvisionedUsers: true, }, mockSCIMUtil: &mockSCIMUtil{ - userSyncEnabled: false, // Dynamic disabled - nonProvisionedUsersAllowed: false, - shouldUseDynamicConfig: true, + userSyncEnabled: false, // Dynamic disabled + nonProvisionedUsersRejected: false, + shouldUseDynamicConfig: true, }, - expectedUserSyncEnabled: false, - expectedNonProvisionedAllowed: false, + expectedUserSyncEnabled: false, + expectedNonProvisionedRejected: false, }, { name: "SCIM util with error - falls back to static config", @@ -1533,13 +1578,13 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { }, staticConfig: &StaticSCIMConfig{ IsUserProvisioningEnabled: true, - AllowNonProvisionedUsers: false, + RejectNonProvisionedUsers: false, }, mockSCIMUtil: &mockSCIMUtil{ shouldReturnError: true, }, - expectedUserSyncEnabled: true, - expectedNonProvisionedAllowed: false, + expectedUserSyncEnabled: true, + expectedNonProvisionedRejected: false, }, } @@ -1559,14 +1604,14 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) { } assert.Equal(t, tt.expectedUserSyncEnabled, userSyncEnabled, "User sync enabled mismatch") - // Test non-provisioned users allowed check - var nonProvisionedAllowed bool + // Test non-provisioned users rejected check + var nonProvisionedReject bool if userSync.scimUtil != nil { - nonProvisionedAllowed = userSync.scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, tt.staticConfig.AllowNonProvisionedUsers) + nonProvisionedReject = userSync.scimUtil.AreNonProvisionedUsersRejected(ctx, orgID, tt.staticConfig.RejectNonProvisionedUsers) } else { - nonProvisionedAllowed = tt.staticConfig.AllowNonProvisionedUsers + nonProvisionedReject = tt.staticConfig.RejectNonProvisionedUsers } - assert.Equal(t, tt.expectedNonProvisionedAllowed, nonProvisionedAllowed, "Non-provisioned users allowed mismatch") + assert.Equal(t, tt.expectedNonProvisionedRejected, nonProvisionedReject, "Non-provisioned users rejected mismatch") }) } } @@ -1745,3 +1790,61 @@ func TestUserSync_NamespaceMappingLogic(t *testing.T) { }) } } + +func TestUserSync_GetUsageStats(t *testing.T) { + userSync := initUserSyncService() + + // Test that GetUsageStats returns zero initially + stats := userSync.GetUsageStats(context.Background()) + + require.NotNil(t, stats) + require.Contains(t, stats, "stats.features.scim.has_successful_login.count") + require.Equal(t, int(0), stats["stats.features.scim.has_successful_login.count"]) + + userSync.scimSuccessfulLogin.Store(true) + + // Test that GetUsageStats returns the updated value + stats = userSync.GetUsageStats(context.Background()) + require.Equal(t, int(1), stats["stats.features.scim.has_successful_login.count"]) +} + +func TestUserSync_SCIMLoginUsageStatSet(t *testing.T) { + userSync := initUserSyncService() + userSync.rejectNonProvisionedUsers = false + userSync.isUserProvisioningEnabled = true + userSync.userService = &usertest.FakeUserService{ + ExpectedUser: &user.User{ + ID: 1, + IsProvisioned: true, + }, + } + userSync.authInfoService = &authinfotest.FakeService{ + ExpectedUserAuth: &login.UserAuth{ + UserId: 1, + AuthModule: login.SAMLAuthModule, + AuthId: "1", + ExternalUID: "test123", + }, + } + + // Check initial counter value + initialStats := userSync.GetUsageStats(context.Background()) + require.Equal(t, int(0), initialStats["stats.features.scim.has_successful_login.count"]) + + // Create identity for validation with matching ExternalUID + identity := &authn.Identity{ + AuthID: "1", + AuthenticatedBy: login.SAMLAuthModule, + ExternalUID: "test123", + ClientParams: authn.ClientParams{SyncUser: true}, + } + + // Call ValidateUserProvisioningHook - this should set the flag to true + err := userSync.ValidateUserProvisioningHook(context.Background(), identity, nil) + require.NoError(t, err) + + // Check that flag was set to true (count should be 1) + finalStats := userSync.GetUsageStats(context.Background()) + finalCount := finalStats["stats.features.scim.has_successful_login.count"].(int) + require.Equal(t, int(1), finalCount) +} diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md index 55425c151e7..f30d3ba7dea 100644 --- a/pkg/services/authz/zanzana/schema/README.md +++ b/pkg/services/authz/zanzana/schema/README.md @@ -15,7 +15,7 @@ them. This is because we want to store the folder tree relations. To grant a user access to a specific folder we store `{ “user”: “user:1”, relation: “read”, object:”folder:” }` -To grant a user access to sub resources of a folder we store ``{ “user”: “user:1”, relation: “resource_read”, object:”folder:”}` with additional context. +To grant a user access to sub resources of a folder we store `{ “user”: “user:1”, relation: “resource_read”, object:”folder:”}` with additional context. This context holds all GroupResources in a list e.g. `{ "subresources": ["dashboard.grafana.app/dashboards", "alerting.grafana.app/rules" ] }`. ## Resource level permissions diff --git a/pkg/services/authz/zanzana/server/server.go b/pkg/services/authz/zanzana/server/server.go index c495f0504ac..1a1d538cc11 100644 --- a/pkg/services/authz/zanzana/server/server.go +++ b/pkg/services/authz/zanzana/server/server.go @@ -11,7 +11,8 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" "google.golang.org/protobuf/types/known/wrapperspb" - dashboardalpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -87,8 +88,21 @@ func (s *Server) getContextuals(subject string) (*openfgav1.ContextualTupleKeys, User: subject, Relation: common.RelationSetView, Object: common.NewGroupResourceIdent( - dashboardalpha1.DashboardResourceInfo.GroupResource().Group, - dashboardalpha1.DashboardResourceInfo.GroupResource().Resource, + dashboardV2alpha1.DashboardResourceInfo.GroupResource().Group, + dashboardV2alpha1.DashboardResourceInfo.GroupResource().Resource, + "", + ), + }, + ) + + contextuals = append( + contextuals, + &openfgav1.TupleKey{ + User: subject, + Relation: common.RelationSetView, + Object: common.NewGroupResourceIdent( + dashboardV2alpha2.DashboardResourceInfo.GroupResource().Group, + dashboardV2alpha2.DashboardResourceInfo.GroupResource().Resource, "", ), }, diff --git a/pkg/services/cloudmigration/cloudmigration.go b/pkg/services/cloudmigration/cloudmigration.go index 7b90fb750d5..a325d736b0f 100644 --- a/pkg/services/cloudmigration/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigration.go @@ -7,6 +7,11 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) +const ( + ResourceStorageTypeFs = "fs" + ResourceStorageTypeDb = "db" +) + type Service interface { // GetToken Returns the cloud migration token if it exists. GetToken(ctx context.Context) (authapi.TokenView, error) diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go index a2342c9338e..163fcfbcb1b 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go @@ -496,23 +496,24 @@ func (s *Service) CreateSnapshot(ctx context.Context, signedInUser *user.SignedI // save snapshot to the db snapshot := cloudmigration.CloudMigrationSnapshot{ - UID: util.GenerateShortUID(), - SessionUID: cmd.SessionUID, - Status: cloudmigration.SnapshotStatusCreating, - EncryptionKey: initResp.EncryptionKey, - GMSSnapshotUID: initResp.SnapshotID, - LocalDir: filepath.Join(s.cfg.CloudMigration.SnapshotFolder, "grafana", "snapshots", initResp.SnapshotID), + UID: util.GenerateShortUID(), + SessionUID: cmd.SessionUID, + Status: cloudmigration.SnapshotStatusCreating, + GMSPublicKey: initResp.GMSPublicKey, + GMSSnapshotUID: initResp.SnapshotID, + Metadata: initResp.Metadata, + EncryptionAlgo: initResp.Algo, + LocalDir: filepath.Join(s.cfg.CloudMigration.SnapshotFolder, "grafana", "snapshots", initResp.SnapshotID), + ResourceStorageType: s.cfg.CloudMigration.ResourceStorageType, } - uid, err := s.store.CreateSnapshot(ctx, snapshot) - if err != nil { + if err := s.store.CreateSnapshot(ctx, snapshot); err != nil { return nil, fmt.Errorf("saving snapshot: %w", err) } - snapshot.UID = uid // Update status to "creating" to ensure the frontend polls from now on if err := s.updateSnapshotWithRetries(ctx, cloudmigration.UpdateSnapshotCmd{ - UID: uid, + UID: snapshot.UID, SessionID: cmd.SessionUID, Status: cloudmigration.SnapshotStatusCreating, }); err != nil { @@ -538,6 +539,7 @@ func (s *Service) CreateSnapshot(ctx context.Context, signedInUser *user.SignedI s.report(asyncCtx, session, gmsclient.EventStartBuildingSnapshot, 0, nil, signedInUser.UserUID) start := time.Now() + err := s.buildSnapshot(asyncCtx, signedInUser, initResp.MaxItemsPerPartition, initResp.Metadata, snapshot, cmd.ResourceTypes) if err != nil { asyncSpan.SetStatus(codes.Error, "error building snapshot") @@ -896,10 +898,12 @@ func (s *Service) deleteLocalFiles(snapshots []cloudmigration.CloudMigrationSnap var err error for _, snapshot := range snapshots { - err = os.RemoveAll(snapshot.LocalDir) - if err != nil { - // in this case we only log the error, don't return it to continue with the process - s.log.Error("deleting migration snapshot files", "err", err) + if snapshot.LocalDir != "" { + err = os.RemoveAll(snapshot.LocalDir) + if err != nil { + // in this case we only log the error, don't return it to continue with the process + s.log.Error("deleting migration snapshot files", "err", err) + } } } return err diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 90d9cc7ce78..3bf16b66ef9 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -103,14 +103,15 @@ func Test_GetSnapshotStatusFromGMS(t *testing.T) { }) require.NoError(t, err) - uid, err := s.store.CreateSnapshot(ctx, cloudmigration.CloudMigrationSnapshot{ - UID: "test uid", + uid := "test uid" + + err = s.store.CreateSnapshot(ctx, cloudmigration.CloudMigrationSnapshot{ + UID: uid, SessionUID: sess.UID, Status: cloudmigration.SnapshotStatusCreating, GMSSnapshotUID: "gms uid", }) require.NoError(t, err) - assert.Equal(t, "test uid", uid) // Make sure status is coming from the db only snapshot, err := s.GetSnapshot(ctx, cloudmigration.GetSnapshotsQuery{ @@ -381,8 +382,9 @@ func Test_OnlyQueriesStatusFromGMSWhenRequired(t *testing.T) { }) require.NoError(t, err) - uid, err := s.store.CreateSnapshot(context.Background(), cloudmigration.CloudMigrationSnapshot{ - UID: uuid.NewString(), + uid := uuid.NewString() + err = s.store.CreateSnapshot(context.Background(), cloudmigration.CloudMigrationSnapshot{ + UID: uid, SessionUID: sess.UID, Status: cloudmigration.SnapshotStatusCreating, GMSSnapshotUID: "gms uid", diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index 8f35242d66e..7d3cbb0161c 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -1,6 +1,7 @@ package cloudmigrationimpl import ( + "bytes" "context" cryptoRand "crypto/rand" "encoding/json" @@ -562,7 +563,7 @@ func (s *Service) buildSnapshot( // Use GMS public key + the grafana generated private key to encrypt snapshot files. snapshotWriter, err := snapshot.NewSnapshotWriter(contracts.AssymetricKeys{ - Public: snapshotMeta.EncryptionKey, + Public: snapshotMeta.GMSPublicKey, Private: privateKey[:], }, crypto.NewNacl(), @@ -605,6 +606,56 @@ func (s *Service) buildSnapshot( } } + switch s.cfg.CloudMigration.ResourceStorageType { + case cloudmigration.ResourceStorageTypeDb: + if err := s.buildSnapshotWithDBStorage(ctx, snapshotMeta.UID, snapshotWriter, resourcesGroupedByType, maxItemsPerPartition); err != nil { + return fmt.Errorf("building snapshot with database storage: %w", err) + } + s.log.Debug(fmt.Sprintf("buildSnapshot: wrote data partitions with database storage in %d ms", time.Since(start).Milliseconds())) + + case cloudmigration.ResourceStorageTypeFs: + if err := s.buildSnapshotWithFSStorage(publicKey[:], metadata, snapshotWriter, resourcesGroupedByType, maxItemsPerPartition); err != nil { + return fmt.Errorf("building snapshot with file system storage: %w", err) + } + s.log.Debug(fmt.Sprintf("buildSnapshot: wrote data partitions with file system storage in %d ms", time.Since(start).Milliseconds())) + + default: + return fmt.Errorf("unknown resource storage type, check your configuration and try again: %q", s.cfg.CloudMigration.ResourceStorageType) + } + + // update snapshot status to pending upload with retries + if err := s.updateSnapshotWithRetries(ctx, cloudmigration.UpdateSnapshotCmd{ + UID: snapshotMeta.UID, + SessionID: snapshotMeta.SessionUID, + Status: cloudmigration.SnapshotStatusPendingUpload, + LocalResourcesToCreate: localSnapshotResource, + PublicKey: publicKey[:], + }); err != nil { + return err + } + + return nil +} + +func (s *Service) buildSnapshotWithDBStorage(ctx context.Context, snapshotUID string, snapshotWriter *snapshot.SnapshotWriter, resourcesGroupedByType map[cloudmigration.MigrateDataType][]snapshot.MigrateDataRequestItemDTO, maxItemsPerPartition uint32) error { + for _, resourceType := range currentMigrationTypes { + i := 0 + for chunk := range slices.Chunk(resourcesGroupedByType[resourceType], int(maxItemsPerPartition)) { + encoded, err := snapshotWriter.EncodePartition(chunk) + if err != nil { + return fmt.Errorf("encoding snapshot partition: %w", err) + } + if err := s.store.StorePartition(ctx, snapshotUID, string(resourceType), i, encoded); err != nil { + return fmt.Errorf("storing partition into database: %w", err) + } + i += 1 + } + } + + return nil +} + +func (s *Service) buildSnapshotWithFSStorage(publicKey, metadata []byte, snapshotWriter *snapshot.SnapshotWriter, resourcesGroupedByType map[cloudmigration.MigrateDataType][]snapshot.MigrateDataRequestItemDTO, maxItemsPerPartition uint32) error { for _, resourceType := range currentMigrationTypes { for chunk := range slices.Chunk(resourcesGroupedByType[resourceType], int(maxItemsPerPartition)) { if err := snapshotWriter.Write(string(resourceType), chunk); err != nil { @@ -613,8 +664,6 @@ func (s *Service) buildSnapshot( } } - s.log.Debug(fmt.Sprintf("buildSnapshot: wrote data files in %d ms", time.Since(start).Milliseconds())) - // Add the grafana generated public key to the index file so gms can use it to decrypt the snapshot files later. // This works because the snapshot files are being encrypted with // the grafana generated private key + the gms public key. @@ -625,18 +674,6 @@ func (s *Service) buildSnapshot( return fmt.Errorf("finishing writing snapshot files and generating index file: %w", err) } - s.log.Debug(fmt.Sprintf("buildSnapshot: finished snapshot in %d ms", time.Since(start).Milliseconds())) - - // update snapshot status to pending upload with retries - if err := s.updateSnapshotWithRetries(ctx, cloudmigration.UpdateSnapshotCmd{ - UID: snapshotMeta.UID, - SessionID: snapshotMeta.SessionUID, - Status: cloudmigration.SnapshotStatusPendingUpload, - LocalResourcesToCreate: localSnapshotResource, - }); err != nil { - return err - } - return nil } @@ -654,13 +691,94 @@ func (s *Service) uploadSnapshot(ctx context.Context, session *cloudmigration.Cl s.log.Debug(fmt.Sprintf("uploadSnapshot: method completed in %d ms", time.Since(start).Milliseconds())) }() + switch s.cfg.CloudMigration.ResourceStorageType { + case cloudmigration.ResourceStorageTypeDb: + if err := s.uploadSnapshotWithDBStorage(ctx, session, snapshotMeta, uploadUrl); err != nil { + return fmt.Errorf("uploading snapshot with database storage: %w", err) + } + + case cloudmigration.ResourceStorageTypeFs: + if err := s.uploadSnapshotWithFSStorage(ctx, session, snapshotMeta, uploadUrl); err != nil { + return fmt.Errorf("uploading snapshot with file system storage: %w", err) + } + + default: + return fmt.Errorf("unknown resource storage type, check your configuration and try again: %q", s.cfg.CloudMigration.ResourceStorageType) + } + + s.log.Info("successfully uploaded snapshot", "snapshotUid", snapshotMeta.UID, "cloud_snapshotUid", snapshotMeta.GMSSnapshotUID) + + // update snapshot status to processing with retries + if err := s.updateSnapshotWithRetries(ctx, cloudmigration.UpdateSnapshotCmd{ + UID: snapshotMeta.UID, + SessionID: snapshotMeta.SessionUID, + Status: cloudmigration.SnapshotStatusProcessing, + }); err != nil { + return err + } + + return nil +} + +func (s *Service) uploadSnapshotWithDBStorage(ctx context.Context, session *cloudmigration.CloudMigrationSession, snapshotMeta *cloudmigration.CloudMigrationSnapshot, uploadUrl string) error { + index, err := s.store.GetIndex(ctx, session.OrgID, snapshotMeta.SessionUID, snapshotMeta.UID) + if err != nil { + return fmt.Errorf("fetching index from database: %w", err) + } + + snapshotIndex := snapshot.Index{ + Version: 1, + EncryptionAlgo: index.EncryptionAlgo, + PublicKey: index.PublicKey, + Metadata: index.Metadata, + Items: make(map[string][]string), + } + + var partitionToFileName = func(resourceType string, partitionNumber int) string { + return fmt.Sprintf("%+v_%+v", resourceType, partitionNumber) + } + + for resourceType, partitionsNumbers := range index.Items { + for _, partitionNumber := range partitionsNumbers { + fileName := partitionToFileName(resourceType, partitionNumber) + snapshotIndex.Items[resourceType] = append(snapshotIndex.Items[resourceType], fileName) + + key := fmt.Sprintf("%d/snapshots/%s/%+v", session.StackID, snapshotMeta.GMSSnapshotUID, fileName) + + partition, err := s.store.GetPartition(ctx, snapshotMeta.UID, resourceType, partitionNumber) + if err != nil { + return fmt.Errorf("fetching partition from database: %w", err) + } + if err = s.objectStorage.PresignedURLUpload(ctx, uploadUrl, key, bytes.NewReader(partition.Data)); err != nil { + return fmt.Errorf("uploading file using presigned url: %w", err) + } + } + } + + key := fmt.Sprintf("%d/snapshots/%s/%s", session.StackID, snapshotMeta.GMSSnapshotUID, "index.json") + + buffer, err := snapshot.EncodeIndex(snapshotIndex) + + if err != nil { + return fmt.Errorf("encoding snapshot index for upload: %w", err) + } + + if err = s.objectStorage.PresignedURLUpload(ctx, uploadUrl, key, bytes.NewReader(buffer)); err != nil { + return fmt.Errorf("uploading index file using presigned url: %w", err) + } + + return nil +} + +func (s *Service) uploadSnapshotWithFSStorage(ctx context.Context, session *cloudmigration.CloudMigrationSession, snapshotMeta *cloudmigration.CloudMigrationSnapshot, uploadUrl string) error { indexFilePath := filepath.Join(snapshotMeta.LocalDir, "index.json") + + start := time.Now() // LocalDir can be set in the configuration, therefore the file path can be set to any path. // nolint:gosec indexFile, err := os.Open(indexFilePath) if err != nil { - // TODO: Clean this notice once we've fixed the HA bug - return fmt.Errorf("opening index files: %w. If you are running Grafana in a highly-available setup, try scaling down to one replica to avoid a known bug: https://github.com/grafana/grafana/issues/107264", err) + return fmt.Errorf("opening index files: %w. If you are running Grafana in a highly-available setup, try setting cloud_migration.resource_storage_type to 'db' or scaling down to one replica", err) } defer func() { if closeErr := indexFile.Close(); closeErr != nil { @@ -679,8 +797,6 @@ func (s *Service) uploadSnapshot(ctx context.Context, session *cloudmigration.Cl } readIndexSpan.End() - s.log.Debug(fmt.Sprintf("uploadSnapshot: read index file in %d ms", time.Since(start).Milliseconds())) - uploadCtx, uploadSpan := s.tracer.Start(ctx, "CloudMigrationService.uploadSnapshot.uploadDataFiles") // Upload the data files. for _, fileNames := range index.Items { @@ -724,16 +840,6 @@ func (s *Service) uploadSnapshot(ctx context.Context, session *cloudmigration.Cl uploadSpan.End() s.log.Debug(fmt.Sprintf("uploadSnapshot: uploaded index file in %d ms", time.Since(start).Milliseconds())) - s.log.Info("successfully uploaded snapshot", "snapshotUid", snapshotMeta.UID, "cloud_snapshotUid", snapshotMeta.GMSSnapshotUID) - - // update snapshot status to processing with retries - if err := s.updateSnapshotWithRetries(ctx, cloudmigration.UpdateSnapshotCmd{ - UID: snapshotMeta.UID, - SessionID: snapshotMeta.SessionUID, - Status: cloudmigration.SnapshotStatusProcessing, - }); err != nil { - return err - } return nil } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/store.go b/pkg/services/cloudmigration/cloudmigrationimpl/store.go index 3d4d3703ed8..316fae7e9bd 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/store.go @@ -12,8 +12,11 @@ type store interface { GetCloudMigrationSessionList(ctx context.Context, orgID int64) ([]*cloudmigration.CloudMigrationSession, error) DeleteMigrationSessionByUID(ctx context.Context, orgID int64, uid string) (*cloudmigration.CloudMigrationSession, []cloudmigration.CloudMigrationSnapshot, error) - CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) (string, error) + CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) error UpdateSnapshot(ctx context.Context, snapshot cloudmigration.UpdateSnapshotCmd) error + GetIndex(ctx context.Context, orgID int64, sessionUID string, snapshotUID string) (cloudmigration.CloudMigrationSnapshotIndex, error) + GetPartition(ctx context.Context, snapshotUID string, resourceType string, partitionNumber int) (cloudmigration.CloudMigrationSnapshotPartition, error) + StorePartition(ctx context.Context, snapshotUID string, resourceType string, partitionNumber int, data []byte) error GetSnapshotByUID(ctx context.Context, orgID int64, sessUid, id string, params cloudmigration.SnapshotResultQueryParams) (*cloudmigration.CloudMigrationSnapshot, error) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go index 0dec65ae14f..20b401ff615 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store.go @@ -121,6 +121,7 @@ func (ss *sqlStore) DeleteMigrationSessionByUID(ctx context.Context, orgID int64 SessionUID: uid, Page: 1, Limit: GetAllSnapshots, + OrgID: orgID, } snapshots, err := ss.GetSnapshotList(ctx, q) if err != nil { @@ -129,12 +130,13 @@ func (ss *sqlStore) DeleteMigrationSessionByUID(ctx context.Context, orgID int64 err = ss.db.InTransaction(ctx, func(ctx context.Context) error { for _, snapshot := range snapshots { - err := ss.deleteSnapshotResources(ctx, snapshot.UID) - if err != nil { + if err := ss.deleteSnapshotResources(ctx, snapshot.UID); err != nil { return fmt.Errorf("deleting snapshot resource from db: %w", err) } - err = ss.deleteSnapshot(ctx, snapshot.UID) - if err != nil { + if err := ss.deleteSnapshotPartitions(ctx, snapshot.UID); err != nil { + return fmt.Errorf("deleting snapshot partitions: %w", err) + } + if err := ss.deleteSnapshot(ctx, snapshot.UID); err != nil { return fmt.Errorf("deleting snapshot from db: %w", err) } } @@ -166,33 +168,33 @@ func (ss *sqlStore) DeleteMigrationSessionByUID(ctx context.Context, orgID int64 return &c, snapshots, nil } -func (ss *sqlStore) CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) (string, error) { +func (ss *sqlStore) CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) error { if snapshot.SessionUID == "" { - return "", fmt.Errorf("sessionUID is required") + return fmt.Errorf("sessionUID is required") } - if snapshot.UID == "" { - snapshot.UID = util.GenerateShortUID() + return fmt.Errorf("snapshot uid is required") } - if err := ss.secretsStore.Set(ctx, secretskv.AllOrganizations, snapshot.UID, secretType, string(snapshot.EncryptionKey)); err != nil { - return "", err + if err := ss.secretsStore.Set(ctx, secretskv.AllOrganizations, snapshot.UID, secretType, string(snapshot.GMSPublicKey)); err != nil { + return err } err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { snapshot.Created = time.Now() snapshot.Updated = time.Now() - _, err := sess.Insert(&snapshot) + _, err := sess.InsertOne(&snapshot) if err != nil { return err } + return nil }) if err != nil { - return "", err + return err } - return snapshot.UID, nil + return nil } // UpdateSnapshot takes a command containing a snapshot uid and any updates to apply to the snapshot. @@ -232,19 +234,133 @@ func (ss *sqlStore) UpdateSnapshot(ctx context.Context, update cloudmigration.Up return err } } + if update.PublicKey != nil { + if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + rawSQL := "UPDATE cloud_migration_snapshot SET public_key=? WHERE session_uid=? AND uid=?" + if _, err := sess.Exec(rawSQL, update.PublicKey, update.SessionID, update.UID); err != nil { + return fmt.Errorf("updating snapshot public key for uid %s: %w", update.UID, err) + } + return nil + }); err != nil { + return err + } + } return nil } -func (ss *sqlStore) deleteSnapshot(ctx context.Context, snapshotUid string) error { - return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - _, err := sess.Delete(cloudmigration.CloudMigrationSnapshot{ - UID: snapshotUid, +func (ss *sqlStore) StorePartition(ctx context.Context, snapshotUID string, resourceType string, partitionNumber int, data []byte) error { + return ss.db.InTransaction(ctx, func(ctx context.Context) error { + return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + _, err := sess.Insert(cloudmigration.CloudMigrationSnapshotPartition{ + SnapshotUID: snapshotUID, + ResourceType: resourceType, + PartitionNumber: partitionNumber, + Data: data, + }) + if err != nil { + return fmt.Errorf("inserting snapshot partition into database: %w", err) + } + return nil }) - return err }) } +func (ss *sqlStore) GetIndex(ctx context.Context, orgID int64, sessionUID string, snapshotUID string) (cloudmigration.CloudMigrationSnapshotIndex, error) { + var snap *cloudmigration.CloudMigrationSnapshot + partitions := make([]cloudmigration.CloudMigrationSnapshotPartition, 0) + + if err := ss.db.InTransaction(ctx, func(ctx context.Context) error { + return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + s, err := ss.getSnapshotByUID(ctx, orgID, sessionUID, snapshotUID) + if err != nil { + return fmt.Errorf("fetching snapshot from database: %w", err) + } + + snap = s + if err := sess.OrderBy("cloud_migration_snapshot_partition.resource_type,cloud_migration_snapshot_partition.partition_number ASC").Find(&partitions, &cloudmigration.CloudMigrationSnapshotPartition{SnapshotUID: snapshotUID}); err != nil { + return fmt.Errorf("fetching partition from database: %w", err) + } + if secret, found, err := ss.secretsStore.Get(ctx, secretskv.AllOrganizations, snap.UID, secretType); err != nil { + return err + } else if !found { + return fmt.Errorf("encryption key not found for snapshot with UID %s", snap.UID) + } else { + snap.GMSPublicKey = []byte(secret) + } + + return nil + }) + }); err != nil { + return cloudmigration.CloudMigrationSnapshotIndex{}, err + } + + partitionsByResourceType := make(map[string][]int) + for _, partition := range partitions { + partitionsByResourceType[partition.ResourceType] = append(partitionsByResourceType[partition.ResourceType], partition.PartitionNumber) + } + + return cloudmigration.CloudMigrationSnapshotIndex{ + EncryptionAlgo: snap.EncryptionAlgo, + PublicKey: snap.PublicKey, + Metadata: snap.Metadata, + Items: partitionsByResourceType, + }, nil +} + +func (ss *sqlStore) GetPartition(ctx context.Context, snapshotUID string, resourceType string, partitionNumber int) (cloudmigration.CloudMigrationSnapshotPartition, error) { + var partition cloudmigration.CloudMigrationSnapshotPartition + + err := ss.db.InTransaction(ctx, func(ctx context.Context) error { + return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + if _, err := sess.Where("snapshot_uid = ? AND resource_type = ? AND partition_number = ?", snapshotUID, resourceType, partitionNumber).Get(&partition); err != nil { + return fmt.Errorf("fetching partition from database: %w", err) + } + return nil + }) + }) + + return partition, err +} + +func (ss *sqlStore) deleteSnapshot(ctx context.Context, snapshotUid string) error { + return ss.db.InTransaction(ctx, func(ctx context.Context) error { + return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + if _, err := sess.Delete(cloudmigration.CloudMigrationSnapshot{ + UID: snapshotUid, + }); err != nil { + return fmt.Errorf("deleting snapshot: %w", err) + } + return nil + }) + }) +} + +func (ss *sqlStore) getSnapshotByUID(ctx context.Context, orgID int64, sessionUID string, snapshotUID string) (*cloudmigration.CloudMigrationSnapshot, error) { + session, err := ss.GetMigrationSessionByUID(ctx, orgID, sessionUID) + if err != nil || session == nil { + return nil, err + } + + // now we get the snapshot + var snapshot cloudmigration.CloudMigrationSnapshot + err = ss.db.WithDbSession(ctx, func(sess *db.Session) error { + exist, err := sess.Where("session_uid=? AND uid=?", sessionUID, snapshotUID).Get(&snapshot) + if err != nil { + return err + } + if !exist { + return cloudmigration.ErrSnapshotNotFound + } + return nil + }) + if err != nil { + return nil, err + } + + return &snapshot, nil +} + func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUid, uid string, params cloudmigration.SnapshotResultQueryParams) (*cloudmigration.CloudMigrationSnapshot, error) { // first we check if the session exists, using orgId and sessionUid session, err := ss.GetMigrationSessionByUID(ctx, orgID, sessionUid) @@ -273,7 +389,7 @@ func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUi } else if !found { return &snapshot, fmt.Errorf("encryption key not found for snapshot with UID %s", snapshot.UID) } else { - snapshot.EncryptionKey = []byte(secret) + snapshot.GMSPublicKey = []byte(secret) } resources, err := ss.getSnapshotResources(ctx, uid, params) @@ -291,6 +407,12 @@ func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, orgID int64, sessionUi // GetSnapshotList returns snapshots without resources included. Use GetSnapshotByUID to get individual snapshot results. // passing GetAllSnapshots will return all the elements regardless of the page func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) { + if query.OrgID == 0 { + return nil, fmt.Errorf("org id is required") + } + if query.SessionUID == "" { + return nil, fmt.Errorf("session uid is required") + } var snapshots = make([]cloudmigration.CloudMigrationSnapshot, 0) err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { sess.Join("INNER", "cloud_migration_session", @@ -310,13 +432,14 @@ func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.Li if err != nil { return nil, err } + for i, snapshot := range snapshots { if secret, found, err := ss.secretsStore.Get(ctx, secretskv.AllOrganizations, snapshot.UID, secretType); err != nil { return nil, err } else if !found { return nil, fmt.Errorf("encryption key not found for snapshot with UID %s", snapshot.UID) } else { - snapshot.EncryptionKey = []byte(secret) + snapshot.GMSPublicKey = []byte(secret) } if stats, err := ss.getSnapshotResourceStats(ctx, snapshot.UID); err != nil { @@ -531,6 +654,17 @@ func (ss *sqlStore) deleteSnapshotResources(ctx context.Context, snapshotUid str }) } +func (ss *sqlStore) deleteSnapshotPartitions(ctx context.Context, snapshotUid string) error { + return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + if _, err := sess.Delete(cloudmigration.CloudMigrationSnapshotPartition{ + SnapshotUID: snapshotUid, + }); err != nil { + return fmt.Errorf("deleting snapshot partitions: %w", err) + } + return nil + }) +} + func (ss *sqlStore) encryptToken(ctx context.Context, cm *cloudmigration.CloudMigrationSession) error { s, err := ss.secretsService.Encrypt(ctx, []byte(cm.AuthToken), secrets.WithoutScope()) if err != nil { diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go index 1c96293e4b7..2d84b257a5a 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go @@ -1,13 +1,18 @@ package cloudmigrationimpl import ( + "bytes" "context" + cryptoRand "crypto/rand" "encoding/base64" "fmt" "strconv" "testing" "github.com/google/uuid" + snapshot "github.com/grafana/grafana-cloud-migration-snapshot/src" + "github.com/grafana/grafana-cloud-migration-snapshot/src/contracts" + "github.com/grafana/grafana-cloud-migration-snapshot/src/infra/crypto" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/cloudmigration" fakeSecrets "github.com/grafana/grafana/pkg/services/secrets/fakes" @@ -15,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/crypto/nacl/box" ) func Test_GetAllCloudMigrationSessions(t *testing.T) { @@ -112,17 +118,18 @@ func Test_SnapshotManagement(t *testing.T) { require.NoError(t, err) // create a snapshot + uid := uuid.NewString() cmr := cloudmigration.CloudMigrationSnapshot{ + UID: uid, SessionUID: session.UID, Status: cloudmigration.SnapshotStatusCreating, } - snapshotUid, err := s.CreateSnapshot(ctx, cmr) + err = s.CreateSnapshot(ctx, cmr) require.NoError(t, err) - require.NotEmpty(t, snapshotUid) //retrieve it from the db - snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{ + snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, uid, cloudmigration.SnapshotResultQueryParams{ ResultPage: 1, ResultLimit: 100, SortColumn: cloudmigration.SortColumnID, @@ -132,11 +139,11 @@ func Test_SnapshotManagement(t *testing.T) { require.Equal(t, cloudmigration.SnapshotStatusCreating, snapshot.Status) // update its status - err = s.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{UID: snapshotUid, Status: cloudmigration.SnapshotStatusCreating, SessionID: session.UID}) + err = s.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{UID: uid, Status: cloudmigration.SnapshotStatusCreating, SessionID: session.UID}) require.NoError(t, err) //retrieve it again - snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{ + snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, uid, cloudmigration.SnapshotResultQueryParams{ ResultPage: 1, ResultLimit: 100, SortColumn: cloudmigration.SortColumnID, @@ -152,11 +159,11 @@ func Test_SnapshotManagement(t *testing.T) { require.Equal(t, *snapshot, snapshots[0]) // delete snapshot - err = s.deleteSnapshot(ctx, snapshotUid) + err = s.deleteSnapshot(ctx, uid) require.NoError(t, err) // now we expect not to find the snapshot - snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{ + snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, uid, cloudmigration.SnapshotResultQueryParams{ ResultPage: 1, ResultLimit: 100, SortColumn: cloudmigration.SortColumnID, @@ -174,12 +181,13 @@ func Test_SnapshotManagement(t *testing.T) { require.NoError(t, err) // create a snapshot - snapshotUid, err := s.CreateSnapshot(ctx, cloudmigration.CloudMigrationSnapshot{ + uid := uuid.NewString() + err = s.CreateSnapshot(ctx, cloudmigration.CloudMigrationSnapshot{ + UID: uid, SessionUID: session.UID, Status: cloudmigration.SnapshotStatusCreating, }) require.NoError(t, err) - require.NotEmpty(t, snapshotUid) // Generate 50,001 test resources in order to test both update conditions (reached the batch limit or reached the end) const numResources = 50001 @@ -196,7 +204,7 @@ func Test_SnapshotManagement(t *testing.T) { // Update the snapshot with the resources to create err = s.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{ - UID: snapshotUid, + UID: uid, Status: cloudmigration.SnapshotStatusPendingUpload, SessionID: session.UID, LocalResourcesToCreate: resources, @@ -204,7 +212,7 @@ func Test_SnapshotManagement(t *testing.T) { require.NoError(t, err) // Get the Snapshot and ensure it's in the right state - snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{ + snapshot, err := s.GetSnapshotByUID(ctx, 1, session.UID, uid, cloudmigration.SnapshotResultQueryParams{ ResultPage: 1, ResultLimit: numResources, SortColumn: cloudmigration.SortColumnID, @@ -226,7 +234,7 @@ func Test_SnapshotManagement(t *testing.T) { // Update the snapshot with the resources to update err = s.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{ - UID: snapshotUid, + UID: uid, Status: cloudmigration.SnapshotStatusFinished, SessionID: session.UID, CloudResourcesToUpdate: snapshot.Resources, @@ -234,7 +242,7 @@ func Test_SnapshotManagement(t *testing.T) { require.NoError(t, err) // Get the Snapshot and ensure it's in the right state - snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, snapshotUid, cloudmigration.SnapshotResultQueryParams{ + snapshot, err = s.GetSnapshotByUID(ctx, 1, session.UID, uid, cloudmigration.SnapshotResultQueryParams{ ResultPage: 1, ResultLimit: numResources, SortColumn: cloudmigration.SortColumnID, @@ -637,7 +645,7 @@ func TestGetSnapshotList(t *testing.T) { }) t.Run("return no snapshots if limit is set to 0", func(t *testing.T) { - snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 1, Limit: 0}) + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, OrgID: 1, Page: 1, Limit: 0}) require.NoError(t, err) assert.Empty(t, snapshots) }) @@ -669,7 +677,7 @@ func TestGetSnapshotList(t *testing.T) { }) t.Run("only the snapshots that belong to a specific session are returned", func(t *testing.T) { - snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: "session-uid-that-doesnt-exist", Page: 1, Limit: 100}) + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: "session-uid-that-doesnt-exist", OrgID: 1, Page: 1, Limit: 100}) require.NoError(t, err) assert.Empty(t, snapshots) }) @@ -680,7 +688,7 @@ func TestGetSnapshotList(t *testing.T) { require.NoError(t, err) // Fetch the snapshots that belong to the deleted session. - snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, Page: 1, Limit: 100}) + snapshots, err := s.GetSnapshotList(ctx, cloudmigration.ListSnapshotsQuery{SessionUID: sessionUID, OrgID: 1, Page: 1, Limit: 100}) require.NoError(t, err) // No snapshots should be returned because the session that @@ -801,3 +809,42 @@ func setUpTest(t *testing.T) (*sqlstore.SQLStore, *sqlStore) { func encodeToken(t string) string { return base64.StdEncoding.EncodeToString([]byte(t)) } + +func TestEncodeDecode(t *testing.T) { + gmsPublicKey, gmsPrivateKey, err := box.GenerateKey(cryptoRand.Reader) + require.NoError(t, err) + + grafanaPublicKey, grafanaPrivateKey, err := box.GenerateKey(cryptoRand.Reader) + require.NoError(t, err) + + snapshotWriter, err := snapshot.NewSnapshotWriter(contracts.AssymetricKeys{ + Public: gmsPublicKey[:], + Private: grafanaPrivateKey[:], + }, + crypto.NewNacl(), + "", + ) + require.NoError(t, err) + + chunk := []snapshot.MigrateDataRequestItemDTO{{ + Type: snapshot.AlertRuleGroupType, + RefID: "foo", + Name: "name", + Data: map[string]any{"a": "b"}, + }} + encoded, err := snapshotWriter.EncodePartition(chunk) + require.NoError(t, err) + + require.NoError(t, snapshotWriter.Write("RESOURCE_TYPE", chunk)) + + reader := snapshot.NewSnapshotReader(contracts.AssymetricKeys{ + Public: grafanaPublicKey[:], + Private: gmsPrivateKey[:], + }, + crypto.NewNacl()) + + partition, err := reader.ReadFile(bytes.NewReader(encoded)) + require.NoError(t, err) + + require.Equal(t, chunk, partition.Items) +} diff --git a/pkg/services/cloudmigration/gmsclient/gms_client_test.go b/pkg/services/cloudmigration/gmsclient/gms_client_test.go index d8acbd0af10..092b9223214 100644 --- a/pkg/services/cloudmigration/gmsclient/gms_client_test.go +++ b/pkg/services/cloudmigration/gmsclient/gms_client_test.go @@ -228,7 +228,7 @@ func Test_StartSnapshot(t *testing.T) { SnapshotID: "uuid", MaxItemsPerPartition: 1024, Algo: "nacl", - EncryptionKey: []uint8{0x66, 0x6f, 0x6f, 0xa}, // foo + GMSPublicKey: []uint8{0x66, 0x6f, 0x6f, 0xa}, // foo Metadata: []uint8{0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xa}, // metadata } diff --git a/pkg/services/cloudmigration/gmsclient/inmemory_client.go b/pkg/services/cloudmigration/gmsclient/inmemory_client.go index 9a176619e87..9d72f187f91 100644 --- a/pkg/services/cloudmigration/gmsclient/inmemory_client.go +++ b/pkg/services/cloudmigration/gmsclient/inmemory_client.go @@ -57,7 +57,7 @@ func (c *memoryClientImpl) StartSnapshot(_ context.Context, sess cloudmigration. c.mx.Unlock() return &cloudmigration.StartSnapshotResponse{ - EncryptionKey: publicKey[:], + GMSPublicKey: publicKey[:], SnapshotID: snapshotUid, MaxItemsPerPartition: 10, Algo: "nacl", diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index a01a33c62d4..d2e4a689bc7 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -35,17 +35,21 @@ type CloudMigrationSession struct { // CloudMigrationSnapshot contains all of the metadata about a snapshot type CloudMigrationSnapshot struct { - ID int64 `xorm:"pk autoincr 'id'"` - UID string `xorm:"uid"` - SessionUID string `xorm:"session_uid"` - Status SnapshotStatus - EncryptionKey []byte `xorm:"-"` // stored in the unified secrets table - LocalDir string `xorm:"local_directory"` - GMSSnapshotUID string `xorm:"gms_snapshot_uid"` - ErrorString string `xorm:"error_string"` - Created time.Time - Updated time.Time - Finished time.Time + ID int64 `xorm:"pk autoincr 'id'"` + UID string `xorm:"uid"` + SessionUID string `xorm:"session_uid"` + Status SnapshotStatus + GMSPublicKey []byte `xorm:"-"` // stored in the unified secrets table + PublicKey []byte `xorm:"public_key"` + LocalDir string `xorm:"local_directory"` + GMSSnapshotUID string `xorm:"gms_snapshot_uid"` + ErrorString string `xorm:"error_string"` + ResourceStorageType string `xorm:"resource_storage_type"` + EncryptionAlgo string `xorm:"encryption_algo"` + Metadata []byte `xorm:"'metadata'"` + Created time.Time + Updated time.Time + Finished time.Time // Stored in the cloud_migration_resource table Resources []CloudMigrationResource `xorm:"-"` @@ -53,6 +57,20 @@ type CloudMigrationSnapshot struct { StatsRollup SnapshotResourceStats `xorm:"-"` } +type CloudMigrationSnapshotPartition struct { + SnapshotUID string `xorm:"snapshot_uid"` + ResourceType string `xorm:"resource_type"` + PartitionNumber int `xorm:"partition_number"` + Data []byte `xorm:"data"` +} + +type CloudMigrationSnapshotIndex struct { + EncryptionAlgo string + PublicKey []byte + Metadata []byte + Items map[string][]int +} + type SnapshotStatus string const ( @@ -226,6 +244,7 @@ type UpdateSnapshotCmd struct { UID string SessionID string Status SnapshotStatus + PublicKey []byte // LocalResourcesToCreate represents the local state of a resource before it has been uploaded to GMS LocalResourcesToCreate []CloudMigrationResource @@ -293,7 +312,7 @@ type StartSnapshotResponse struct { SnapshotID string `json:"snapshotID"` MaxItemsPerPartition uint32 `json:"maxItemsPerPartition"` Algo string `json:"algo"` - EncryptionKey []byte `json:"encryptionKey"` + GMSPublicKey []byte `json:"encryptionKey"` Metadata []byte `json:"metadata"` } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index dc759ffd0d3..acdbc55b896 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -6,11 +6,11 @@ import ( "fmt" "net/http" - "github.com/open-feature/go-sdk/openfeature" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" claims "github.com/grafana/authlib/types" + "github.com/open-feature/go-sdk/openfeature" authnClients "github.com/grafana/grafana/pkg/services/authn/clients" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -88,77 +88,85 @@ func CopyWithReqContext(ctx context.Context) context.Context { func (h *ContextHandler) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - // Don't modify context so that the auth middleware span doesn't get propagated as a parent elsewhere - _, span := tracing.Start(ctx, "Auth - Middleware") - reqContext := &contextmodel.ReqContext{ - Context: web.FromContext(ctx), - SignedInUser: &user.SignedInUser{ - Permissions: map[int64]map[string][]string{}, - }, - IsSignedIn: false, - AllowAnonymous: false, - SkipDSCache: false, - Logger: log.New("context"), - UseSessionStorageRedirect: h.features.IsEnabledGlobally(featuremgmt.FlagUseSessionStorageForRedirection), - } + span := trace.SpanFromContext(ctx) + ctx = h.setRequestContext(ctx) - // inject ReqContext in the context - ctx = context.WithValue(ctx, reqContextKey{}, reqContext) - // store list of possible auth header in context - ctx = WithAuthHTTPHeaders(ctx, h.cfg) - // Set the context for the http.Request.Context - // This modifies both r and reqContext.Req since they point to the same value - *reqContext.Req = *reqContext.Req.WithContext(ctx) + // Preserve the original span so the setRequestContext span doesn't get propagated as a parent of the rest of the request + ctx = trace.ContextWithSpan(ctx, span) - ctx = trace.ContextWithSpan(reqContext.Req.Context(), span) - traceID := tracing.TraceIDFromContext(ctx, false) - if traceID != "" { - reqContext.Logger = reqContext.Logger.New("traceID", traceID) - } - - id, err := h.authenticator.Authenticate(ctx, &authn.Request{HTTPRequest: reqContext.Req}) - if err != nil { - // Hack: set all errors on LookupTokenErr, so we can check it in auth middlewares - reqContext.LookupTokenErr = err - } else { - reqContext.SignedInUser = id.SignedInUser() - reqContext.UserToken = id.SessionToken - reqContext.IsSignedIn = !reqContext.IsAnonymous - reqContext.AllowAnonymous = reqContext.IsAnonymous - reqContext.IsRenderCall = id.IsAuthenticatedBy(login.RenderModule) - ctx = identity.WithRequester(ctx, id) - } - - h.excludeSensitiveHeadersFromRequest(reqContext.Req) - - reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserID, "orgId", reqContext.OrgID, "uname", reqContext.Login) - span.AddEvent("user", trace.WithAttributes( - attribute.String("uname", reqContext.Login), - attribute.Int64("orgId", reqContext.OrgID), - attribute.Int64("userId", reqContext.UserID), - )) - - if h.cfg.IDResponseHeaderEnabled && reqContext.SignedInUser != nil { - reqContext.Resp.Before(h.addIDHeaderEndOfRequestFunc(reqContext.SignedInUser)) - } - - // Set open feature evaluation context with namespace - ns := "default" - if id != nil { - ns = id.Namespace - } - evalCtx := openfeature.NewEvaluationContext(ns, map[string]any{ - "namespace": ns, - }) - ctx = openfeature.MergeTransactionContext(ctx, evalCtx) - - // End the span to make next handlers not wrapped within middleware span - span.End() next.ServeHTTP(w, r.WithContext(ctx)) }) } +func (h *ContextHandler) setRequestContext(ctx context.Context) context.Context { + ctx, span := tracing.Start(ctx, "ContextHandler.setRequestContext") + defer span.End() + + reqContext := &contextmodel.ReqContext{ + Context: web.FromContext(ctx), + SignedInUser: &user.SignedInUser{ + Permissions: map[int64]map[string][]string{}, + }, + IsSignedIn: false, + AllowAnonymous: false, + SkipDSCache: false, + Logger: log.New("context"), + UseSessionStorageRedirect: h.features.IsEnabledGlobally(featuremgmt.FlagUseSessionStorageForRedirection), + } + + // inject ReqContext in the context + ctx = context.WithValue(ctx, reqContextKey{}, reqContext) + // store list of possible auth header in context + ctx = WithAuthHTTPHeaders(ctx, h.cfg) + // Set the context for the http.Request.Context + // This modifies both r and reqContext.Req since they point to the same value + *reqContext.Req = *reqContext.Req.WithContext(ctx) + + traceID := tracing.TraceIDFromContext(ctx, false) + if traceID != "" { + reqContext.Logger = reqContext.Logger.New("traceID", traceID) + } + + id, err := h.authenticator.Authenticate(ctx, &authn.Request{HTTPRequest: reqContext.Req}) + if err != nil { + // Hack: set all errors on LookupTokenErr, so we can check it in auth middlewares + reqContext.LookupTokenErr = err + } else { + reqContext.SignedInUser = id.SignedInUser() + reqContext.UserToken = id.SessionToken + reqContext.IsSignedIn = !reqContext.IsAnonymous + reqContext.AllowAnonymous = reqContext.IsAnonymous + reqContext.IsRenderCall = id.IsAuthenticatedBy(login.RenderModule) + ctx = identity.WithRequester(ctx, id) + } + + h.excludeSensitiveHeadersFromRequest(reqContext.Req) + + reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserID, "orgId", reqContext.OrgID, "uname", reqContext.Login) + span.AddEvent("user", trace.WithAttributes( + attribute.String("uname", reqContext.Login), + attribute.Int64("orgId", reqContext.OrgID), + attribute.Int64("userId", reqContext.UserID), + )) + + if h.cfg.IDResponseHeaderEnabled && reqContext.SignedInUser != nil { + reqContext.Resp.Before(h.addIDHeaderEndOfRequestFunc(reqContext.SignedInUser)) + } + + // Set open feature evaluation context with namespace + ns := "default" + if id != nil { + ns = id.Namespace + } + evalCtx := openfeature.NewEvaluationContext(ns, map[string]any{ + "namespace": ns, + }) + ctx = openfeature.MergeTransactionContext(ctx, evalCtx) + + return ctx +} + func (h *ContextHandler) excludeSensitiveHeadersFromRequest(req *http.Request) { req.Header.Del(authnClients.ExtJWTAuthenticationHeaderName) req.Header.Del(authnClients.ExtJWTAuthorizationHeaderName) diff --git a/pkg/services/dashboards/service/client/client.go b/pkg/services/dashboards/service/client/client.go index 401d4773d16..01d0e020f62 100644 --- a/pkg/services/dashboards/service/client/client.go +++ b/pkg/services/dashboards/service/client/client.go @@ -5,7 +5,6 @@ import ( "fmt" "sync" - "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -18,11 +17,13 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/search/sort" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/prometheus/client_golang/prometheus" ) type K8sClientFactory func(ctx context.Context, version string) client.K8sHandler @@ -44,8 +45,9 @@ func NewK8sClientWithFallback( sorter sort.Service, dual dualwrite.Service, reg prometheus.Registerer, + features featuremgmt.FeatureToggles, ) *K8sClientWithFallback { - newClientFunc := newK8sClientFactory(cfg, restConfigProvider, dashboardStore, userService, resourceClient, sorter, dual) + newClientFunc := newK8sClientFactory(cfg, restConfigProvider, dashboardStore, userService, resourceClient, sorter, dual, features) return &K8sClientWithFallback{ K8sHandler: newClientFunc(context.Background(), dashboardv0.VERSION), newClientFunc: newClientFunc, @@ -112,6 +114,7 @@ func newK8sClientFactory( resourceClient resource.ResourceClient, sorter sort.Service, dual dualwrite.Service, + features featuremgmt.FeatureToggles, ) K8sClientFactory { clientCache := make(map[string]client.K8sHandler) cacheMutex := &sync.RWMutex{} @@ -149,7 +152,7 @@ func newK8sClientFactory( } span.AddEvent("Creating new client") - newClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), gvr, restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient, sorter) + newClient := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), gvr, restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient, sorter, features) clientCache[version] = newClient return newClient diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index e3d17b5bedf..de7624545c2 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -387,7 +387,7 @@ func ProvideDashboardServiceImpl( serverLockService *serverlock.ServerLockService, kvstore kvstore.KVStore, ) (*DashboardServiceImpl, error) { - k8sclient := dashboardclient.NewK8sClientWithFallback(cfg, restConfigProvider, dashboardStore, userService, resourceClient, sorter, dual, r) + k8sclient := dashboardclient.NewK8sClientWithFallback(cfg, restConfigProvider, dashboardStore, userService, resourceClient, sorter, dual, r, features) dashSvc := &DashboardServiceImpl{ cfg: cfg, log: log.New("dashboard-service"), @@ -482,7 +482,7 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc } func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Context, libraryPanelUID string, orgID int64) ([]*dashboards.DashboardRef, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesLibraryPanels) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesLibraryPanelConnections) { res, err := dr.k8sclient.Search(ctx, orgID, &resourcepb.ResourceSearchRequest{ Options: &resourcepb.ListOptions{ Fields: []*resourcepb.Requirement{ diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 0767ecf6734..fa1d91e8a0b 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -2934,7 +2934,7 @@ func TestGetDashboardsByLibraryPanelUID(t *testing.T) { dashboardStore: &fakeStore, folderService: folderSvc, ac: actest.FakeAccessControl{ExpectedEvaluate: true}, - features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagKubernetesLibraryPanels), + features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagKubernetesLibraryPanelConnections), publicDashboardService: fakePublicDashboardService, k8sclient: k8sCliMock, } diff --git a/pkg/services/dashboardversion/dashverimpl/dashver.go b/pkg/services/dashboardversion/dashverimpl/dashver.go index c36288073b3..9b16131262e 100644 --- a/pkg/services/dashboardversion/dashverimpl/dashver.go +++ b/pkg/services/dashboardversion/dashverimpl/dashver.go @@ -61,6 +61,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.Dash userService, unified, sorter, + features, ), dashSvc: dashboardService, log: log.New("dashboard-version"), diff --git a/pkg/services/featuremgmt/codeowners.go b/pkg/services/featuremgmt/codeowners.go index e4647a59a6f..75e868fd57b 100644 --- a/pkg/services/featuremgmt/codeowners.go +++ b/pkg/services/featuremgmt/codeowners.go @@ -8,6 +8,7 @@ const ( grafanaAppPlatformSquad codeowner = "@grafana/grafana-app-platform-squad" grafanaDashboardsSquad codeowner = "@grafana/dashboards-squad" grafanaDatavizSquad codeowner = "@grafana/dataviz-squad" + grafanaDataProSquad codeowner = "@grafana/datapro" grafanaFrontendPlatformSquad codeowner = "@grafana/grafana-frontend-platform" grafanaFrontendSearchNavOrganise codeowner = "@grafana/grafana-search-navigate-organise" grafanaBackendServicesSquad codeowner = "@grafana/grafana-backend-services-squad" diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 0663e997deb..b7ee60b6e55 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -261,14 +261,6 @@ var ( Stage: FeatureStagePrivatePreview, Owner: grafanaPluginsPlatformSquad, }, - { - Name: "pluginsDetailsRightPanel", - Description: "Enables right panel for the plugins details page", - Stage: FeatureStageGeneralAvailability, - FrontendOnly: true, - Expression: "true", - Owner: grafanaPluginsPlatformSquad, - }, { Name: "sqlDatasourceDatabaseSelection", Description: "Enables previous SQL data source dataset dropdown behavior", @@ -420,13 +412,6 @@ var ( FrontendOnly: false, Owner: grafanaObservabilityLogsSquad, }, - { - Name: "externalCorePlugins", - Description: "Allow core plugins to be loaded as external", - Stage: FeatureStageGeneralAvailability, - Owner: grafanaPluginsPlatformSquad, - Expression: "true", // enabled by default - }, { Name: "externalServiceAccounts", Description: "Automatic service account and token setup for plugins", @@ -484,6 +469,13 @@ var ( Owner: grafanaAppPlatformSquad, RequiresRestart: true, // changes the API routing }, + { + Name: "kubernetesLibraryPanelConnections", + Description: "Routes library panel connections requests from /api to using search", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + RequiresRestart: true, // changes the API routing + }, { Name: "kubernetesDashboards", Description: "Use the kubernetes API in the frontend for dashboards", @@ -585,6 +577,14 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaAlertingSquad, }, + { + Name: "alertingProvenanceLockWrites", + Description: "Enables a feature to avoid issues with concurrent writes to the alerting provenance table in MySQL", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + }, { Name: "alertmanagerRemotePrimary", Description: "Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager.", @@ -659,6 +659,13 @@ var ( FrontendOnly: true, Owner: grafanaDatavizSquad, }, + { + Name: "timeComparison", + Description: "Enables time comparison option in supported panels", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaDatavizSquad, + }, { Name: "logsInfiniteScrolling", Description: "Enables infinite scrolling for the Logs panel in Explore and Dashboards", @@ -795,6 +802,13 @@ var ( FrontendOnly: false, Owner: grafanaDatasourcesCoreServicesSquad, }, + { + Name: "sqlExpressionsColumnAutoComplete", + Description: "Enables column autocomplete for SQL Expressions", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaDataProSquad, + }, { Name: "groupToNestedTableTransformation", Description: "Enables the group to nested table transformation", @@ -1278,6 +1292,15 @@ var ( HideFromDocs: true, Expression: "false", }, + { + Name: "alertingAIFeedback", + Description: "Enable AI-generated feedback from the Grafana UI.", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + Expression: "false", + }, { Name: "alertingAIImproveAlertRules", Description: "Enable AI-improve alert rules labels and annotations.", @@ -1447,12 +1470,6 @@ var ( RequiresRestart: true, Expression: "true", }, - { - Name: "pluginsCDNSyncLoader", - Description: "Loads plugins from CDN synchronously", - Stage: FeatureStageExperimental, - Owner: grafanaPluginsPlatformSquad, - }, { Name: "alertingJiraIntegration", Description: "Enables the new Jira integration for contact points in cloud alert managers.", @@ -1570,7 +1587,7 @@ var ( { Name: "localeFormatPreference", Description: "Specifies the locale so the correct format for numbers and dates can be shown", - Stage: FeatureStageExperimental, + Stage: FeatureStagePublicPreview, Owner: grafanaFrontendPlatformSquad, }, { @@ -1698,6 +1715,14 @@ var ( HideFromAdminPage: true, HideFromDocs: true, }, + { + Name: "kubernetesAuthnMutation", + Description: "Enables create, delete, and update mutations for resources owned by IAM identity", + Stage: FeatureStageExperimental, + Owner: identityAccessTeam, + HideFromAdminPage: true, + HideFromDocs: true, + }, { Name: "restoreDashboards", Description: "Enables restore deleted dashboards feature", @@ -1819,6 +1844,30 @@ var ( Expression: "false", RequiresRestart: true, }, + { + Name: "unifiedStorageSearchDualReaderEnabled", + Description: "Enable dual reader for unified storage search", + Stage: FeatureStageExperimental, + Owner: grafanaSearchAndStorageSquad, + HideFromAdminPage: true, + HideFromDocs: true, + }, + { + Name: "dashboardDsAdHocFiltering", + Description: "Enables adhoc filtering support for the dashboard datasource", + Stage: FeatureStageExperimental, + Owner: grafanaDataProSquad, + FrontendOnly: true, + }, + { + Name: "alertmanagerRemoteSecondaryWithRemoteState", + Description: "Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications.", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + Expression: "false", + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 56fbe4a7545..d22d7e5b728 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -33,7 +33,6 @@ faroDatasourceSelector,preview,@grafana/app-o11y,false,false,true enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true pluginsFrontendSandbox,privatePreview,@grafana/plugins-platform-backend,false,false,false -pluginsDetailsRightPanel,GA,@grafana/plugins-platform-backend,false,false,true sqlDatasourceDatabaseSelection,preview,@grafana/oss-big-tent,false,false,true recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true @@ -54,7 +53,6 @@ aiGeneratedDashboardChanges,experimental,@grafana/dashboards-squad,false,false,t reportingRetries,preview,@grafana/grafana-operator-experience-squad,false,true,false sseGroupByDatasource,experimental,@grafana/observability-metrics,false,false,false lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false -externalCorePlugins,GA,@grafana/plugins-platform-backend,false,false,false externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false panelMonitoring,GA,@grafana/dataviz-squad,false,false,true enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false @@ -62,6 +60,7 @@ disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad formatString,GA,@grafana/dataviz-squad,false,false,true kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false +kubernetesLibraryPanelConnections,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true kubernetesClientDashboardsFolders,GA,@grafana/grafana-app-platform-squad,false,false,false dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false @@ -77,6 +76,7 @@ cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-e prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true addFieldFromCalculationStatFunctions,GA,@grafana/dataviz-squad,false,false,true alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,false,false,false +alertingProvenanceLockWrites,experimental,@grafana/alerting-squad,false,false,false alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,false,false,false annotationPermissionUpdate,GA,@grafana/identity-access-team,false,false,false extractFieldsNameDeduplication,experimental,@grafana/dataviz-squad,false,false,true @@ -87,6 +87,7 @@ dashboardNewLayouts,experimental,@grafana/dashboards-squad,false,false,true panelFilterVariable,experimental,@grafana/dashboards-squad,false,false,true pdfTables,preview,@grafana/grafana-operator-experience-squad,false,false,false canvasPanelPanZoom,preview,@grafana/dataviz-squad,false,false,true +timeComparison,experimental,@grafana/dataviz-squad,false,false,true logsInfiniteScrolling,GA,@grafana/observability-logs,false,false,true logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false @@ -104,6 +105,7 @@ scopeApi,experimental,@grafana/grafana-app-platform-squad,false,false,false promQLScope,GA,@grafana/oss-big-tent,false,false,false logQLScope,privatePreview,@grafana/observability-logs,false,false,false sqlExpressions,privatePreview,@grafana/grafana-datasources-core-services,false,false,false +sqlExpressionsColumnAutoComplete,experimental,@grafana/datapro,false,false,true groupToNestedTableTransformation,GA,@grafana/dataviz-squad,false,false,true newPDFRendering,GA,@grafana/grafana-operator-experience-squad,false,false,false tlsMemcached,GA,@grafana/grafana-operator-experience-squad,false,false,false @@ -167,6 +169,7 @@ crashDetection,experimental,@grafana/observability-traces-and-profiling,false,fa alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false alertingAIGenAlertRules,experimental,@grafana/alerting-squad,false,false,false +alertingAIFeedback,experimental,@grafana/alerting-squad,false,false,false alertingAIImproveAlertRules,experimental,@grafana/alerting-squad,false,false,false alertingAIGenTemplates,experimental,@grafana/alerting-squad,false,false,false alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false,false,false @@ -189,7 +192,6 @@ datasourceConnectionsTab,privatePreview,@grafana/plugins-platform-backend,false, fetchRulesUsingPost,experimental,@grafana/alerting-squad,false,false,false newLogsPanel,experimental,@grafana/observability-logs,false,false,true grafanaconThemes,GA,@grafana/grafana-frontend-platform,false,true,false -pluginsCDNSyncLoader,experimental,@grafana/plugins-platform-backend,false,false,false alertingJiraIntegration,experimental,@grafana/alerting-squad,false,false,true useScopesNavigationEndpoint,experimental,@grafana/grafana-frontend-platform,false,false,true scopeSearchAllLevels,experimental,@grafana/grafana-frontend-platform,false,false,false @@ -204,7 +206,7 @@ alertingMigrationUI,GA,@grafana/alerting-squad,false,false,true alertingImportYAMLUI,GA,@grafana/alerting-squad,false,false,true unifiedStorageHistoryPruner,GA,@grafana/search-and-storage,false,false,false azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false -localeFormatPreference,experimental,@grafana/grafana-frontend-platform,false,false,false +localeFormatPreference,preview,@grafana/grafana-frontend-platform,false,false,false unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false extensionSidebar,experimental,@grafana/observability-logs,false,false,true alertingRulePermanentlyDelete,GA,@grafana/alerting-squad,false,false,true @@ -221,6 +223,7 @@ alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,fal alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true kubernetesAuthzApis,experimental,@grafana/identity-access-team,false,false,false +kubernetesAuthnMutation,experimental,@grafana/identity-access-team,false,false,false restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false skipTokenRotationIfRecent,GA,@grafana/identity-access-team,false,false,false alertEnrichment,experimental,@grafana/alerting-squad,false,false,false @@ -235,3 +238,6 @@ enablePluginImporter,experimental,@grafana/plugins-platform-backend,false,false, otelLogsFormatting,experimental,@grafana/observability-logs,false,false,true alertingNotificationHistory,experimental,@grafana/alerting-squad,false,false,false pluginAssetProvider,experimental,@grafana/plugins-platform-backend,false,true,false +unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,false,false,false +dashboardDsAdHocFiltering,experimental,@grafana/datapro,false,false,true +alertmanagerRemoteSecondaryWithRemoteState,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 151ced45e79..7def9808abb 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -143,10 +143,6 @@ const ( // Enables the plugins frontend sandbox FlagPluginsFrontendSandbox = "pluginsFrontendSandbox" - // FlagPluginsDetailsRightPanel - // Enables right panel for the plugins details page - FlagPluginsDetailsRightPanel = "pluginsDetailsRightPanel" - // FlagSqlDatasourceDatabaseSelection // Enables previous SQL data source dataset dropdown behavior FlagSqlDatasourceDatabaseSelection = "sqlDatasourceDatabaseSelection" @@ -227,10 +223,6 @@ const ( // Enables running Loki queries in parallel FlagLokiRunQueriesInParallel = "lokiRunQueriesInParallel" - // FlagExternalCorePlugins - // Allow core plugins to be loaded as external - FlagExternalCorePlugins = "externalCorePlugins" - // FlagExternalServiceAccounts // Automatic service account and token setup for plugins FlagExternalServiceAccounts = "externalServiceAccounts" @@ -259,6 +251,10 @@ const ( // Routes library panel requests from /api to the /apis endpoint FlagKubernetesLibraryPanels = "kubernetesLibraryPanels" + // FlagKubernetesLibraryPanelConnections + // Routes library panel connections requests from /api to using search + FlagKubernetesLibraryPanelConnections = "kubernetesLibraryPanelConnections" + // FlagKubernetesDashboards // Use the kubernetes API in the frontend for dashboards FlagKubernetesDashboards = "kubernetesDashboards" @@ -319,6 +315,10 @@ const ( // Enable Grafana to sync configuration and state with a remote Alertmanager. FlagAlertmanagerRemoteSecondary = "alertmanagerRemoteSecondary" + // FlagAlertingProvenanceLockWrites + // Enables a feature to avoid issues with concurrent writes to the alerting provenance table in MySQL + FlagAlertingProvenanceLockWrites = "alertingProvenanceLockWrites" + // FlagAlertmanagerRemotePrimary // Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager. FlagAlertmanagerRemotePrimary = "alertmanagerRemotePrimary" @@ -359,6 +359,10 @@ const ( // Allow pan and zoom in canvas panel FlagCanvasPanelPanZoom = "canvasPanelPanZoom" + // FlagTimeComparison + // Enables time comparison option in supported panels + FlagTimeComparison = "timeComparison" + // FlagLogsInfiniteScrolling // Enables infinite scrolling for the Logs panel in Explore and Dashboards FlagLogsInfiniteScrolling = "logsInfiniteScrolling" @@ -427,6 +431,10 @@ const ( // Enables SQL Expressions, which can execute SQL queries against data source results. FlagSqlExpressions = "sqlExpressions" + // FlagSqlExpressionsColumnAutoComplete + // Enables column autocomplete for SQL Expressions + FlagSqlExpressionsColumnAutoComplete = "sqlExpressionsColumnAutoComplete" + // FlagGroupToNestedTableTransformation // Enables the group to nested table transformation FlagGroupToNestedTableTransformation = "groupToNestedTableTransformation" @@ -679,6 +687,10 @@ const ( // Enable AI-generated alert rules. FlagAlertingAIGenAlertRules = "alertingAIGenAlertRules" + // FlagAlertingAIFeedback + // Enable AI-generated feedback from the Grafana UI. + FlagAlertingAIFeedback = "alertingAIFeedback" + // FlagAlertingAIImproveAlertRules // Enable AI-improve alert rules labels and annotations. FlagAlertingAIImproveAlertRules = "alertingAIImproveAlertRules" @@ -767,10 +779,6 @@ const ( // Enables the temporary themes for GrafanaCon FlagGrafanaconThemes = "grafanaconThemes" - // FlagPluginsCDNSyncLoader - // Loads plugins from CDN synchronously - FlagPluginsCDNSyncLoader = "pluginsCDNSyncLoader" - // FlagAlertingJiraIntegration // Enables the new Jira integration for contact points in cloud alert managers. FlagAlertingJiraIntegration = "alertingJiraIntegration" @@ -895,6 +903,10 @@ const ( // Registers AuthZ /apis endpoint FlagKubernetesAuthzApis = "kubernetesAuthzApis" + // FlagKubernetesAuthnMutation + // Enables create, delete, and update mutations for resources owned by IAM identity + FlagKubernetesAuthnMutation = "kubernetesAuthnMutation" + // FlagRestoreDashboards // Enables restore deleted dashboards feature FlagRestoreDashboards = "restoreDashboards" @@ -950,4 +962,16 @@ const ( // FlagPluginAssetProvider // Allows decoupled core plugins to load from the Grafana CDN FlagPluginAssetProvider = "pluginAssetProvider" + + // FlagUnifiedStorageSearchDualReaderEnabled + // Enable dual reader for unified storage search + FlagUnifiedStorageSearchDualReaderEnabled = "unifiedStorageSearchDualReaderEnabled" + + // FlagDashboardDsAdHocFiltering + // Enables adhoc filtering support for the dashboard datasource + FlagDashboardDsAdHocFiltering = "dashboardDsAdHocFiltering" + + // FlagAlertmanagerRemoteSecondaryWithRemoteState + // Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications. + FlagAlertmanagerRemoteSecondaryWithRemoteState = "alertmanagerRemoteSecondaryWithRemoteState" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 588999aa8a5..6d738596653 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -6,7 +6,7 @@ { "metadata": { "name": "addFieldFromCalculationStatFunctions", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-03T14:39:58Z" }, "spec": { @@ -20,7 +20,7 @@ { "metadata": { "name": "aiGeneratedDashboardChanges", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-03-05T12:01:31Z" }, "spec": { @@ -33,7 +33,7 @@ { "metadata": { "name": "alertEnrichment", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-06-06T12:16:07Z" }, "spec": { @@ -48,7 +48,7 @@ { "metadata": { "name": "alertRuleRestore", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-05T14:15:26Z" }, "spec": { @@ -61,7 +61,7 @@ { "metadata": { "name": "alertRuleUseFiredAtForStartsAt", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-22T11:16:38Z" }, "spec": { @@ -74,11 +74,8 @@ { "metadata": { "name": "alertingAIAnalyzeCentralStateHistory", - "resourceVersion": "1752215431801", - "creationTimestamp": "2025-07-07T14:40:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enable AI-analyze central state history.", @@ -89,14 +86,26 @@ "expression": "false" } }, + { + "metadata": { + "name": "alertingAIFeedback", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enable AI-generated feedback from the Grafana UI.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true, + "hideFromDocs": true, + "expression": "false" + } + }, { "metadata": { "name": "alertingAIGenAlertRules", - "resourceVersion": "1752215431801", - "creationTimestamp": "2025-07-07T14:40:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enable AI-generated alert rules.", @@ -110,11 +119,8 @@ { "metadata": { "name": "alertingAIGenTemplates", - "resourceVersion": "1752215431801", - "creationTimestamp": "2025-07-07T14:40:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enable AI-generated alerting templates.", @@ -128,11 +134,8 @@ { "metadata": { "name": "alertingAIImproveAlertRules", - "resourceVersion": "1752215431801", - "creationTimestamp": "2025-07-07T14:40:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-11 06:30:31.801024 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enable AI-improve alert rules labels and annotations.", @@ -146,7 +149,7 @@ { "metadata": { "name": "alertingBacktesting", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-12-14T14:44:14Z" }, "spec": { @@ -158,7 +161,7 @@ { "metadata": { "name": "alertingBulkActionsInUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-24T14:49:59Z" }, "spec": { @@ -174,7 +177,7 @@ { "metadata": { "name": "alertingCentralAlertHistory", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-29T15:01:38Z" }, "spec": { @@ -187,7 +190,7 @@ { "metadata": { "name": "alertingDisableSendAlertsExternal", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-23T12:29:19Z" }, "spec": { @@ -201,7 +204,7 @@ { "metadata": { "name": "alertingFilterV2", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-11T11:29:26Z" }, "spec": { @@ -214,7 +217,7 @@ { "metadata": { "name": "alertingImportAlertmanagerAPI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-06-10T08:32:50Z" }, "spec": { @@ -229,7 +232,7 @@ { "metadata": { "name": "alertingImportYAMLUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-05-21T15:59:41Z" }, "spec": { @@ -243,7 +246,7 @@ { "metadata": { "name": "alertingJiraIntegration", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-14T12:22:04Z" }, "spec": { @@ -257,7 +260,7 @@ { "metadata": { "name": "alertingListViewV2", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-24T14:40:49Z" }, "spec": { @@ -270,7 +273,7 @@ { "metadata": { "name": "alertingListViewV2PreviewToggle", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-22T08:50:34Z" }, "spec": { @@ -283,7 +286,7 @@ { "metadata": { "name": "alertingMigrationUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-14T16:40:05Z" }, "spec": { @@ -297,8 +300,8 @@ { "metadata": { "name": "alertingNotificationHistory", - "resourceVersion": "1752682072771", - "creationTimestamp": "2025-07-16T16:07:52Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enables the notification history feature", @@ -312,7 +315,7 @@ { "metadata": { "name": "alertingNotificationsStepMode", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-22T11:07:45Z" }, "spec": { @@ -326,7 +329,7 @@ { "metadata": { "name": "alertingPrometheusRulesPrimary", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-27T12:27:16Z" }, "spec": { @@ -336,10 +339,24 @@ "frontend": true } }, + { + "metadata": { + "name": "alertingProvenanceLockWrites", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enables a feature to avoid issues with concurrent writes to the alerting provenance table in MySQL", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "alertingQueryAndExpressionsStepMode", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-26T06:33:14Z" }, "spec": { @@ -353,7 +370,7 @@ { "metadata": { "name": "alertingQueryOptimization", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-10T20:52:58Z" }, "spec": { @@ -366,7 +383,7 @@ { "metadata": { "name": "alertingRulePermanentlyDelete", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-03T11:18:25Z" }, "spec": { @@ -382,7 +399,7 @@ { "metadata": { "name": "alertingRuleRecoverDeleted", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-27T14:39:26Z" }, "spec": { @@ -398,7 +415,7 @@ { "metadata": { "name": "alertingRuleVersionHistoryRestore", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-17T12:25:32Z" }, "spec": { @@ -414,7 +431,7 @@ { "metadata": { "name": "alertingSaveStateCompressed", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-27T17:47:33Z" }, "spec": { @@ -427,7 +444,7 @@ { "metadata": { "name": "alertingSaveStatePeriodic", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-23T16:03:30Z" }, "spec": { @@ -439,7 +456,7 @@ { "metadata": { "name": "alertingUIOptimizeReducer", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-18T10:59:00Z" }, "spec": { @@ -453,7 +470,7 @@ { "metadata": { "name": "alertmanagerRemotePrimary", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -465,7 +482,7 @@ { "metadata": { "name": "alertmanagerRemoteSecondary", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -476,23 +493,23 @@ }, { "metadata": { - "name": "angularDeprecationUI", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-08-29T14:05:47Z", - "deletionTimestamp": "2025-06-27T11:02:00Z" + "name": "alertmanagerRemoteSecondaryWithRemoteState", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { - "description": "Display Angular warnings in dashboards and panels", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "expression": "true" + "description": "Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true, + "hideFromDocs": true, + "expression": "false" } }, { "metadata": { "name": "annotationPermissionUpdate", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-31T13:30:13Z" }, "spec": { @@ -505,7 +522,7 @@ { "metadata": { "name": "appPlatformGrpcClientAuth", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-14T10:47:18Z" }, "spec": { @@ -519,7 +536,7 @@ { "metadata": { "name": "assetSriChecks", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-04T10:56:35Z" }, "spec": { @@ -532,7 +549,7 @@ { "metadata": { "name": "authZGRPCServer", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-13T09:41:35Z" }, "spec": { @@ -546,7 +563,7 @@ { "metadata": { "name": "awsAsyncQueryCaching", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-21T15:34:07Z" }, "spec": { @@ -559,11 +576,8 @@ { "metadata": { "name": "awsDatasourcesTempCredentials", - "resourceVersion": "1752523189825", - "creationTimestamp": "2023-07-06T15:06:11Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-14 19:59:49.825959 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-07-06T15:06:11Z" }, "spec": { "description": "Support temporary security credentials in AWS plugins for Grafana Cloud customers", @@ -575,7 +589,7 @@ { "metadata": { "name": "azureMonitorDisableLogLimit", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-24T13:32:09Z" }, "spec": { @@ -588,7 +602,7 @@ { "metadata": { "name": "azureMonitorEnableUserAuth", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-27T14:01:54Z" }, "spec": { @@ -601,7 +615,7 @@ { "metadata": { "name": "azureMonitorLogsBuilderEditor", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-02T14:15:25Z" }, "spec": { @@ -614,7 +628,7 @@ { "metadata": { "name": "azureMonitorPrometheusExemplars", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-06T16:53:17Z" }, "spec": { @@ -627,7 +641,7 @@ { "metadata": { "name": "cachingOptimizeSerializationMemoryUsage", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-12T16:56:49Z" }, "spec": { @@ -639,7 +653,7 @@ { "metadata": { "name": "canvasPanelNesting", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-05-31T19:03:34Z" }, "spec": { @@ -653,7 +667,7 @@ { "metadata": { "name": "canvasPanelPanZoom", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-02T19:52:21Z" }, "spec": { @@ -666,7 +680,7 @@ { "metadata": { "name": "cloudRBACRoles", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-10T13:19:01Z" }, "spec": { @@ -682,7 +696,7 @@ { "metadata": { "name": "cloudWatchBatchQueries", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-20T19:09:41Z" }, "spec": { @@ -694,7 +708,7 @@ { "metadata": { "name": "cloudWatchCrossAccountQuerying", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-11-28T11:39:12Z" }, "spec": { @@ -708,7 +722,7 @@ { "metadata": { "name": "cloudWatchNewLabelParsing", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-05T15:57:56Z" }, "spec": { @@ -721,7 +735,7 @@ { "metadata": { "name": "cloudWatchRoundUpEndTime", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-27T15:10:28Z" }, "spec": { @@ -734,7 +748,7 @@ { "metadata": { "name": "configurableSchedulerTick", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-26T16:44:12Z" }, "spec": { @@ -748,7 +762,7 @@ { "metadata": { "name": "correlations", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-09-16T13:14:27Z" }, "spec": { @@ -762,7 +776,7 @@ { "metadata": { "name": "crashDetection", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-12T15:07:27Z" }, "spec": { @@ -775,7 +789,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV1", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -787,7 +801,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV2", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -796,10 +810,23 @@ "codeowner": "@grafana/grafana-app-platform-squad" } }, + { + "metadata": { + "name": "dashboardDsAdHocFiltering", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enables adhoc filtering support for the dashboard datasource", + "stage": "experimental", + "codeowner": "@grafana/datapro", + "frontend": true + } + }, { "metadata": { "name": "dashboardNewLayouts", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-23T08:55:45Z" }, "spec": { @@ -812,7 +839,7 @@ { "metadata": { "name": "dashboardScene", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-13T08:51:21Z" }, "spec": { @@ -826,7 +853,7 @@ { "metadata": { "name": "dashboardSceneForViewers", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-02T19:02:25Z" }, "spec": { @@ -840,7 +867,7 @@ { "metadata": { "name": "dashboardSceneSolo", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-11T08:08:47Z" }, "spec": { @@ -854,7 +881,7 @@ { "metadata": { "name": "dashboardSchemaValidationLogging", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -866,7 +893,7 @@ { "metadata": { "name": "dashgpt", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-08-30T20:22:05Z" }, "spec": { @@ -880,7 +907,7 @@ { "metadata": { "name": "dataplaneAggregator", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-08-09T08:41:07Z" }, "spec": { @@ -893,7 +920,7 @@ { "metadata": { "name": "dataplaneFrontendFallback", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-04-07T21:13:19Z" }, "spec": { @@ -908,7 +935,7 @@ { "metadata": { "name": "datasourceAPIServers", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-19T08:28:27Z" }, "spec": { @@ -921,7 +948,7 @@ { "metadata": { "name": "datasourceConnectionsTab", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-21T17:39:48Z" }, "spec": { @@ -934,7 +961,7 @@ { "metadata": { "name": "datasourceQueryTypes", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-23T16:46:28Z" }, "spec": { @@ -947,7 +974,7 @@ { "metadata": { "name": "disableClassicHTTPHistogram", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-18T19:37:44Z" }, "spec": { @@ -961,7 +988,7 @@ { "metadata": { "name": "disableEnvelopeEncryption", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-05-24T08:34:47Z" }, "spec": { @@ -975,7 +1002,7 @@ { "metadata": { "name": "disableNumericMetricsSortingInExpressions", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-16T14:52:47Z" }, "spec": { @@ -988,7 +1015,7 @@ { "metadata": { "name": "disableSSEDataplane", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-04-12T16:24:34Z" }, "spec": { @@ -1000,7 +1027,7 @@ { "metadata": { "name": "editPanelCSVDragAndDrop", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-01-24T09:43:44Z" }, "spec": { @@ -1013,7 +1040,7 @@ { "metadata": { "name": "elasticsearchCrossClusterSearch", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-12T22:20:04Z" }, "spec": { @@ -1025,7 +1052,7 @@ { "metadata": { "name": "elasticsearchImprovedParsing", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-15T17:05:54Z" }, "spec": { @@ -1037,25 +1064,8 @@ { "metadata": { "name": "enableAppChromeExtensions", - "resourceVersion": "1750235111726", - "creationTimestamp": "2025-06-18T08:25:11Z" - }, - "spec": { - "description": "Set this to true to enable all app chrome extensions registered by plugins.", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "hideFromAdminPage": true, - "hideFromDocs": true, - "expression": "false" - } - }, - { - "metadata": { - "name": "enableAppChromeExtensions", - "resourceVersion": "1751899214406", - "creationTimestamp": "2025-07-07T14:40:14Z", - "deletionTimestamp": "2025-07-07T15:15:21Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Set this to true to enable all app chrome extensions registered by plugins.", @@ -1070,7 +1080,7 @@ { "metadata": { "name": "enableDatagridEditing", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-04-24T14:46:31Z" }, "spec": { @@ -1083,7 +1093,7 @@ { "metadata": { "name": "enableExtensionsAdminPage", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-05T15:55:10Z" }, "spec": { @@ -1096,7 +1106,7 @@ { "metadata": { "name": "enableNativeHTTPHistogram", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-03T18:23:55Z" }, "spec": { @@ -1110,8 +1120,8 @@ { "metadata": { "name": "enablePluginImporter", - "resourceVersion": "1752591728321", - "creationTimestamp": "2025-07-15T15:02:08Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Set this to true to use the new PluginImporter functionality", @@ -1126,11 +1136,8 @@ { "metadata": { "name": "enableSCIM", - "resourceVersion": "1750952338509", - "creationTimestamp": "2024-11-07T14:38:46Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-26 15:38:58.509301321 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2024-11-07T14:38:46Z" }, "spec": { "description": "Enables SCIM support for user and group management", @@ -1141,7 +1148,7 @@ { "metadata": { "name": "enableScopesInMetricsExplore", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-06T13:11:33Z" }, "spec": { @@ -1155,7 +1162,7 @@ { "metadata": { "name": "exploreLogsAggregatedMetrics", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1168,7 +1175,7 @@ { "metadata": { "name": "exploreLogsLimitedTimeRange", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1181,7 +1188,7 @@ { "metadata": { "name": "exploreLogsShardSplitting", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1194,7 +1201,7 @@ { "metadata": { "name": "exploreMetricsRelatedLogs", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-05T16:28:43Z" }, "spec": { @@ -1207,7 +1214,7 @@ { "metadata": { "name": "expressionParser", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-17T00:59:11Z" }, "spec": { @@ -1220,7 +1227,7 @@ { "metadata": { "name": "extensionSidebar", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-03T10:16:35Z" }, "spec": { @@ -1230,39 +1237,10 @@ "frontend": true } }, - { - "metadata": { - "name": "extensionsReadOnlyProxy", - "resourceVersion": "1750434297879", - "creationTimestamp": "2025-05-06T04:55:23Z", - "deletionTimestamp": "2025-06-30T08:24:11Z" - }, - "spec": { - "description": "Use proxy-based read-only objects for plugin extensions instead of deep cloning", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "externalCorePlugins", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-09-22T08:50:13Z" - }, - "spec": { - "description": "Allow core plugins to be loaded as external", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "expression": "true" - } - }, { "metadata": { "name": "externalServiceAccounts", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-09-28T07:26:37Z" }, "spec": { @@ -1275,7 +1253,7 @@ { "metadata": { "name": "extraThemes", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-05-10T13:37:04Z", "deletionTimestamp": "2025-05-20T08:18:08Z" }, @@ -1289,7 +1267,7 @@ { "metadata": { "name": "extractFieldsNameDeduplication", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-02T15:47:42Z" }, "spec": { @@ -1299,24 +1277,10 @@ "frontend": true } }, - { - "metadata": { - "name": "failWrongDSUID", - "resourceVersion": "1750434297879", - "creationTimestamp": "2024-06-20T10:56:39Z", - "deletionTimestamp": "2025-06-27T13:43:58Z" - }, - "spec": { - "description": "Throws an error if a data source has an invalid UIDs", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "expression": "true" - } - }, { "metadata": { "name": "faroDatasourceSelector", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-05-05T00:35:10Z" }, "spec": { @@ -1329,7 +1293,7 @@ { "metadata": { "name": "featureHighlights", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-02-03T11:53:23Z" }, "spec": { @@ -1343,7 +1307,7 @@ { "metadata": { "name": "featureToggleAdminPage", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-18T20:43:32Z" }, "spec": { @@ -1357,7 +1321,7 @@ { "metadata": { "name": "feedbackButton", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-02T17:08:15Z" }, "spec": { @@ -1370,7 +1334,7 @@ { "metadata": { "name": "fetchRulesUsingPost", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-29T12:17:44Z" }, "spec": { @@ -1384,8 +1348,8 @@ { "metadata": { "name": "foldersAppPlatformAPI", - "resourceVersion": "1751377081192", - "creationTimestamp": "2025-07-01T13:38:01Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enables use of app platform API for folders", @@ -1400,7 +1364,7 @@ { "metadata": { "name": "formatString", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-13T18:17:12Z" }, "spec": { @@ -1414,7 +1378,7 @@ { "metadata": { "name": "grafanaAPIServerEnsureKubectlAccess", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-12-06T20:21:21Z" }, "spec": { @@ -1428,7 +1392,7 @@ { "metadata": { "name": "grafanaAPIServerWithExperimentalAPIs", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-06T18:55:22Z" }, "spec": { @@ -1442,7 +1406,7 @@ { "metadata": { "name": "grafanaAdvisor", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-20T10:08:00Z" }, "spec": { @@ -1454,7 +1418,7 @@ { "metadata": { "name": "grafanaManagedRecordingRules", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-22T17:53:16Z", "deletionTimestamp": "2025-05-19T10:15:49Z" }, @@ -1469,7 +1433,7 @@ { "metadata": { "name": "grafanaconThemes", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-06T11:08:04Z" }, "spec": { @@ -1485,7 +1449,7 @@ { "metadata": { "name": "groupAttributeSync", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-09T15:29:43Z" }, "spec": { @@ -1498,7 +1462,7 @@ { "metadata": { "name": "groupByVariable", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-14T17:18:04Z" }, "spec": { @@ -1512,7 +1476,7 @@ { "metadata": { "name": "groupToNestedTableTransformation", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-07T14:28:26Z" }, "spec": { @@ -1526,7 +1490,7 @@ { "metadata": { "name": "grpcServer", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-09-26T20:25:34Z" }, "spec": { @@ -1539,11 +1503,8 @@ { "metadata": { "name": "improvedExternalSessionHandling", - "resourceVersion": "1751355094344", - "creationTimestamp": "2024-09-17T10:54:39Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-01 07:31:34.344238 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2024-09-17T10:54:39Z" }, "spec": { "description": "Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves.", @@ -1556,11 +1517,8 @@ { "metadata": { "name": "improvedExternalSessionHandlingSAML", - "resourceVersion": "1751355094344", - "creationTimestamp": "2025-01-09T17:02:49Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-01 07:31:34.344238 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-01-09T17:02:49Z" }, "spec": { "description": "Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly.", @@ -1573,7 +1531,7 @@ { "metadata": { "name": "individualCookiePreferences", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-02-21T10:19:07Z" }, "spec": { @@ -1585,7 +1543,7 @@ { "metadata": { "name": "infinityRunQueriesInParallel", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-14T12:54:04Z" }, "spec": { @@ -1597,7 +1555,7 @@ { "metadata": { "name": "influxdbBackendMigration", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-02-09T18:26:16Z", "deletionTimestamp": "2023-01-17T14:11:26Z" }, @@ -1612,7 +1570,7 @@ { "metadata": { "name": "influxdbRunQueriesInParallel", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-01T10:58:24Z" }, "spec": { @@ -1624,7 +1582,7 @@ { "metadata": { "name": "influxqlStreamingParser", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-29T17:29:35Z" }, "spec": { @@ -1636,7 +1594,7 @@ { "metadata": { "name": "investigationsBackend", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-18T08:31:03Z" }, "spec": { @@ -1649,7 +1607,7 @@ { "metadata": { "name": "inviteUserExperimental", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-07T19:09:59Z" }, "spec": { @@ -1661,27 +1619,10 @@ "hideFromDocs": true } }, - { - "metadata": { - "name": "jaegerBackendMigration", - "resourceVersion": "1751465665226", - "creationTimestamp": "2024-11-15T14:40:20Z", - "deletionTimestamp": "2025-07-07T14:12:35Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-02 14:14:25.226989 +0000 UTC" - } - }, - "spec": { - "description": "Enables querying the Jaeger data source without the proxy", - "stage": "GA", - "codeowner": "@grafana/oss-big-tent", - "expression": "true" - } - }, { "metadata": { "name": "jitterAlertRulesWithinGroups", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-18T18:48:11Z" }, "spec": { @@ -1695,7 +1636,7 @@ { "metadata": { "name": "k8SFolderCounts", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1708,7 +1649,7 @@ { "metadata": { "name": "k8SFolderMove", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1721,7 +1662,7 @@ { "metadata": { "name": "kubernetesAggregator", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-12T20:59:35Z" }, "spec": { @@ -1734,7 +1675,7 @@ { "metadata": { "name": "kubernetesAggregatorCapTokenAuth", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-05-15T18:14:23Z" }, "spec": { @@ -1744,10 +1685,27 @@ "requiresRestart": true } }, + { + "metadata": { + "name": "kubernetesAuthnMutation", + "resourceVersion": "1753454405614", + "creationTimestamp": "2025-07-25T14:12:51Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-07-25 14:40:05.614358 +0000 UTC" + } + }, + "spec": { + "description": "Enables create, delete, and update mutations for resources owned by IAM identity", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "kubernetesAuthzApis", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-06-18T07:43:01Z" }, "spec": { @@ -1761,7 +1719,7 @@ { "metadata": { "name": "kubernetesClientDashboardsFolders", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-18T23:11:26Z" }, "spec": { @@ -1774,7 +1732,7 @@ { "metadata": { "name": "kubernetesDashboards", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-05T14:34:23Z" }, "spec": { @@ -1787,7 +1745,7 @@ { "metadata": { "name": "kubernetesFeatureToggles", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-18T05:32:44Z" }, "spec": { @@ -1798,10 +1756,23 @@ "hideFromAdminPage": true } }, + { + "metadata": { + "name": "kubernetesLibraryPanelConnections", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Routes library panel connections requests from /api to using search", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, { "metadata": { "name": "kubernetesLibraryPanels", - "resourceVersion": "1750714411267", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-06-25T22:21:56Z" }, "spec": { @@ -1814,7 +1785,7 @@ { "metadata": { "name": "kubernetesSnapshots", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-12-05T22:31:49Z" }, "spec": { @@ -1827,19 +1798,19 @@ { "metadata": { "name": "localeFormatPreference", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-31T13:59:07Z" }, "spec": { "description": "Specifies the locale so the correct format for numbers and dates can be shown", - "stage": "experimental", + "stage": "preview", "codeowner": "@grafana/grafana-frontend-platform" } }, { "metadata": { "name": "localizationForPlugins", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-31T04:38:38Z" }, "spec": { @@ -1851,7 +1822,7 @@ { "metadata": { "name": "logQLScope", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-11T11:53:24Z" }, "spec": { @@ -1866,7 +1837,7 @@ { "metadata": { "name": "logRequestsInstrumentedAsUnknown", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-06-10T08:56:55Z" }, "spec": { @@ -1878,7 +1849,7 @@ { "metadata": { "name": "logRowsPopoverMenu", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-16T09:48:10Z" }, "spec": { @@ -1892,7 +1863,7 @@ { "metadata": { "name": "logsContextDatasourceUi", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-01-27T14:12:01Z" }, "spec": { @@ -1907,7 +1878,7 @@ { "metadata": { "name": "logsExploreTableDefaultVisualization", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-02T15:28:15Z" }, "spec": { @@ -1920,7 +1891,7 @@ { "metadata": { "name": "logsExploreTableVisualisation", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-12T13:52:42Z" }, "spec": { @@ -1934,7 +1905,7 @@ { "metadata": { "name": "logsInfiniteScrolling", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-09T10:54:03Z" }, "spec": { @@ -1948,7 +1919,7 @@ { "metadata": { "name": "logsPanelControls", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-07T14:38:55Z" }, "spec": { @@ -1962,7 +1933,7 @@ { "metadata": { "name": "lokiExperimentalStreaming", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-06-19T10:03:51Z" }, "spec": { @@ -1974,7 +1945,7 @@ { "metadata": { "name": "lokiLabelNamesQueryApi", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-13T14:31:41Z" }, "spec": { @@ -1987,7 +1958,7 @@ { "metadata": { "name": "lokiLogsDataplane", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-13T07:58:00Z" }, "spec": { @@ -1996,39 +1967,10 @@ "codeowner": "@grafana/observability-logs" } }, - { - "metadata": { - "name": "lokiPredefinedOperations", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-06-02T10:52:36Z", - "deletionTimestamp": "2025-06-27T08:08:44Z" - }, - "spec": { - "description": "Adds predefined query operations to Loki query editor", - "stage": "experimental", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "lokiQueryHints", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-12-18T20:43:16Z", - "deletionTimestamp": "2025-06-12T12:14:47Z" - }, - "spec": { - "description": "Enables query hints for Loki", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "frontend": true, - "expression": "true" - } - }, { "metadata": { "name": "lokiQuerySplitting", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-02-09T17:27:02Z" }, "spec": { @@ -2040,24 +1982,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "lokiQuerySplittingConfig", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-03-20T15:51:36Z", - "deletionTimestamp": "2025-06-27T09:32:43Z" - }, - "spec": { - "description": "Give users the option to configure split durations for Loki queries", - "stage": "experimental", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, { "metadata": { "name": "lokiRunQueriesInParallel", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-09-19T09:34:01Z" }, "spec": { @@ -2069,7 +1997,7 @@ { "metadata": { "name": "lokiShardSplitting", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-23T11:21:03Z" }, "spec": { @@ -2079,24 +2007,10 @@ "frontend": true } }, - { - "metadata": { - "name": "lokiStructuredMetadata", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-11-16T16:06:14Z", - "deletionTimestamp": "2025-06-27T09:00:53Z" - }, - "spec": { - "description": "Enables the loki data source to request structured metadata from the Loki server", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "expression": "true" - } - }, { "metadata": { "name": "managedDualWriter", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-19T14:50:39Z" }, "spec": { @@ -2110,7 +2024,7 @@ { "metadata": { "name": "metricsFromProfiles", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-09T10:55:28Z" }, "spec": { @@ -2123,7 +2037,7 @@ { "metadata": { "name": "mlExpressions", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-13T17:37:50Z" }, "spec": { @@ -2135,7 +2049,7 @@ { "metadata": { "name": "multiTenantFrontend", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-25T09:24:25Z" }, "spec": { @@ -2147,11 +2061,8 @@ { "metadata": { "name": "multiTenantTempCredentials", - "resourceVersion": "1752523189825", - "creationTimestamp": "2025-04-02T20:25:50Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-14 19:59:49.825959 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-04-02T20:25:50Z" }, "spec": { "description": "use multi-tenant path for awsTempCredentials", @@ -2163,7 +2074,7 @@ { "metadata": { "name": "mysqlAnsiQuotes", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-10-12T11:43:35Z" }, "spec": { @@ -2172,24 +2083,10 @@ "codeowner": "@grafana/search-and-storage" } }, - { - "metadata": { - "name": "nanoGit", - "resourceVersion": "1750434297879", - "creationTimestamp": "2025-06-17T17:07:30Z", - "deletionTimestamp": "2025-07-09T11:53:17Z" - }, - "spec": { - "description": "Use experimental git library for provisioning", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true - } - }, { "metadata": { "name": "nestedFolders", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-10-26T14:15:14Z" }, "spec": { @@ -2202,7 +2099,7 @@ { "metadata": { "name": "newDashboardSharingComponent", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-03T15:02:18Z" }, "spec": { @@ -2216,7 +2113,7 @@ { "metadata": { "name": "newDashboardWithFiltersAndGroupBy", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-04T11:25:21Z" }, "spec": { @@ -2230,7 +2127,7 @@ { "metadata": { "name": "newFiltersUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-08-30T12:48:13Z" }, "spec": { @@ -2243,11 +2140,8 @@ { "metadata": { "name": "newInfluxDSConfigPageDesign", - "resourceVersion": "1750787021637", - "creationTimestamp": "2025-06-25T16:39:54Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-24 17:43:41.637607 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-06-25T16:39:54Z" }, "spec": { "description": "Enables new design for the InfluxDB data source configuration page", @@ -2259,7 +2153,7 @@ { "metadata": { "name": "newLogsPanel", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-02-04T17:40:17Z" }, "spec": { @@ -2272,11 +2166,8 @@ { "metadata": { "name": "newPDFRendering", - "resourceVersion": "1750821734038", - "creationTimestamp": "2024-02-08T12:09:34Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2024-02-08T12:09:34Z" }, "spec": { "description": "New implementation for the dashboard-to-PDF rendering", @@ -2288,11 +2179,8 @@ { "metadata": { "name": "newShareReportDrawer", - "resourceVersion": "1750821734038", - "creationTimestamp": "2025-02-17T19:05:46Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-02-17T19:05:46Z" }, "spec": { "description": "Enables the report creation drawer in a dashboard", @@ -2305,7 +2193,7 @@ { "metadata": { "name": "oauthRequireSubClaim", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-03-25T13:22:24Z" }, "spec": { @@ -2319,7 +2207,7 @@ { "metadata": { "name": "onPremToCloudMigrations", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-22T16:09:08Z" }, "spec": { @@ -2332,8 +2220,8 @@ { "metadata": { "name": "otelLogsFormatting", - "resourceVersion": "1750152612973", - "creationTimestamp": "2025-06-17T09:30:12Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Applies OTel formatting templates to displayed logs", @@ -2345,7 +2233,7 @@ { "metadata": { "name": "panelFilterVariable", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-03T12:15:54Z" }, "spec": { @@ -2359,7 +2247,7 @@ { "metadata": { "name": "panelMonitoring", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-10-09T05:19:08Z" }, "spec": { @@ -2373,7 +2261,7 @@ { "metadata": { "name": "panelTitleSearch", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-02-15T18:26:03Z" }, "spec": { @@ -2386,7 +2274,7 @@ { "metadata": { "name": "passwordlessMagicLinkAuthentication", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-14T13:50:55Z" }, "spec": { @@ -2400,11 +2288,8 @@ { "metadata": { "name": "pdfTables", - "resourceVersion": "1750821734038", - "creationTimestamp": "2023-11-06T13:39:22Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-11-06T13:39:22Z" }, "spec": { "description": "Enables generating table data as PDF in reporting", @@ -2415,7 +2300,7 @@ { "metadata": { "name": "permissionsFilterRemoveSubquery", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-08-02T07:39:25Z" }, "spec": { @@ -2427,7 +2312,7 @@ { "metadata": { "name": "pinNavItems", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-10T11:40:03Z" }, "spec": { @@ -2440,7 +2325,7 @@ { "metadata": { "name": "playlistsReconciler", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-20T03:09:31Z" }, "spec": { @@ -2450,28 +2335,11 @@ "requiresRestart": true } }, - { - "metadata": { - "name": "pluginLoadingRefactor", - "resourceVersion": "1752218524617", - "creationTimestamp": "2025-07-11T07:22:04Z", - "deletionTimestamp": "2025-07-11T07:23:34Z" - }, - "spec": { - "description": "Set this to true to use the new plugin loading functionality", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "hideFromAdminPage": true, - "hideFromDocs": true, - "expression": "false" - } - }, { "metadata": { "name": "pluginAssetProvider", - "resourceVersion": "1752486584712", - "creationTimestamp": "2025-07-14T09:49:44Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Allows decoupled core plugins to load from the Grafana CDN", @@ -2486,7 +2354,7 @@ { "metadata": { "name": "pluginProxyPreserveTrailingSlash", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-05T11:36:14Z" }, "spec": { @@ -2499,7 +2367,7 @@ { "metadata": { "name": "pluginsAutoUpdate", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-16T11:44:39Z" }, "spec": { @@ -2508,36 +2376,10 @@ "codeowner": "@grafana/plugins-platform-backend" } }, - { - "metadata": { - "name": "pluginsCDNSyncLoader", - "resourceVersion": "1750434297879", - "creationTimestamp": "2025-02-07T10:07:08Z" - }, - "spec": { - "description": "Loads plugins from CDN synchronously", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend" - } - }, - { - "metadata": { - "name": "pluginsDetailsRightPanel", - "resourceVersion": "1750434297879", - "creationTimestamp": "2024-08-13T09:55:30Z" - }, - "spec": { - "description": "Enables right panel for the plugins details page", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "expression": "true" - } - }, { "metadata": { "name": "pluginsFrontendSandbox", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-06-05T08:51:36Z" }, "spec": { @@ -2549,7 +2391,7 @@ { "metadata": { "name": "pluginsSkipHostEnvVars", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-11-15T17:09:14Z" }, "spec": { @@ -2561,7 +2403,7 @@ { "metadata": { "name": "pluginsSriChecks", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-04T12:55:09Z" }, "spec": { @@ -2574,7 +2416,7 @@ { "metadata": { "name": "preferLibraryPanelTitle", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-06-17T11:21:21Z" }, "spec": { @@ -2587,7 +2429,7 @@ { "metadata": { "name": "preinstallAutoUpdate", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-07T12:14:25Z" }, "spec": { @@ -2600,7 +2442,7 @@ { "metadata": { "name": "preserveDashboardStateWhenNavigating", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-27T12:28:06Z" }, "spec": { @@ -2614,7 +2456,7 @@ { "metadata": { "name": "promQLScope", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-01-29T20:22:17Z" }, "spec": { @@ -2629,7 +2471,7 @@ { "metadata": { "name": "prometheusAzureOverrideAudience", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-05-30T15:43:32Z", "deletionTimestamp": "2023-07-16T21:30:14Z" }, @@ -2643,7 +2485,7 @@ { "metadata": { "name": "prometheusCodeModeMetricNamesSearch", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-04T20:38:23Z" }, "spec": { @@ -2656,7 +2498,7 @@ { "metadata": { "name": "prometheusSpecialCharsInLabelValues", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-18T21:31:08Z" }, "spec": { @@ -2669,7 +2511,7 @@ { "metadata": { "name": "provisioning", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-22T09:03:50Z" }, "spec": { @@ -2682,11 +2524,8 @@ { "metadata": { "name": "provisioningSecretsService", - "resourceVersion": "1752501981893", - "creationTimestamp": "2025-07-14T14:05:50Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-14 14:06:21.89322 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Experimental feature to use the secrets service for provisioning instead of the legacy secrets", @@ -2698,11 +2537,8 @@ { "metadata": { "name": "publicDashboardsEmailSharing", - "resourceVersion": "1750821734038", - "creationTimestamp": "2023-01-03T19:45:15Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-01-03T19:45:15Z" }, "spec": { "description": "Enables public dashboard sharing to be restricted to only allowed emails", @@ -2715,11 +2551,8 @@ { "metadata": { "name": "publicDashboardsScene", - "resourceVersion": "1750821734038", - "creationTimestamp": "2024-03-22T14:48:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2024-03-22T14:48:21Z" }, "spec": { "description": "Enables public dashboard rendering using scenes", @@ -2732,12 +2565,9 @@ { "metadata": { "name": "queryLibrary", - "resourceVersion": "1750876431546", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-10-07T18:31:45Z", - "deletionTimestamp": "2023-03-20T16:00:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 18:33:51.546111 +0000 UTC" - } + "deletionTimestamp": "2023-03-20T16:00:14Z" }, "spec": { "description": "Enables Query Library feature in Explore", @@ -2748,7 +2578,7 @@ { "metadata": { "name": "queryService", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2761,7 +2591,7 @@ { "metadata": { "name": "queryServiceFromExplore", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-02T10:00:33Z" }, "spec": { @@ -2774,7 +2604,7 @@ { "metadata": { "name": "queryServiceFromUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2787,7 +2617,7 @@ { "metadata": { "name": "queryServiceRewrite", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2800,7 +2630,7 @@ { "metadata": { "name": "recordedQueriesMulti", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-06-14T12:34:22Z" }, "spec": { @@ -2813,7 +2643,7 @@ { "metadata": { "name": "refactorVariablesTimeRange", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-06-06T13:12:09Z" }, "spec": { @@ -2826,9 +2656,8 @@ { "metadata": { "name": "regressionTransformation", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-11-24T14:49:16Z", - "deletionTimestamp": "2025-07-01T13:24:02Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-11-24T14:49:16Z" }, "spec": { "description": "Enables regression analysis transformation", @@ -2840,7 +2669,7 @@ { "metadata": { "name": "reloadDashboardsOnParamsChange", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-25T12:56:54Z" }, "spec": { @@ -2854,11 +2683,8 @@ { "metadata": { "name": "renderAuthJWT", - "resourceVersion": "1750821734038", - "creationTimestamp": "2023-04-03T16:53:38Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-04-03T16:53:38Z" }, "spec": { "description": "Uses JWT-based auth for rendering instead of relying on remote cache", @@ -2870,11 +2696,8 @@ { "metadata": { "name": "rendererDisableAppPluginsPreload", - "resourceVersion": "1750821734038", - "creationTimestamp": "2025-02-24T14:43:06Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-02-24T14:43:06Z" }, "spec": { "description": "Disable pre-loading app plugins when the request is coming from the renderer", @@ -2888,11 +2711,8 @@ { "metadata": { "name": "reportingRetries", - "resourceVersion": "1750821734038", - "creationTimestamp": "2023-08-31T07:47:47Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-06-25 03:22:14.038814 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2023-08-31T07:47:47Z" }, "spec": { "description": "Enables rendering retries for the reporting feature", @@ -2904,7 +2724,7 @@ { "metadata": { "name": "restoreDashboards", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-05-23T14:35:54Z" }, "spec": { @@ -2918,7 +2738,7 @@ { "metadata": { "name": "rolePickerDrawer", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-26T12:51:38Z" }, "spec": { @@ -2930,7 +2750,7 @@ { "metadata": { "name": "scopeApi", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-11-27T07:58:25Z" }, "spec": { @@ -2944,7 +2764,7 @@ { "metadata": { "name": "scopeFilters", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-03-05T15:41:19Z" }, "spec": { @@ -2958,7 +2778,7 @@ { "metadata": { "name": "scopeSearchAllLevels", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-14T07:42:16Z" }, "spec": { @@ -2972,7 +2792,7 @@ { "metadata": { "name": "secretsManagementAppPlatform", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-19T09:25:14Z" }, "spec": { @@ -2984,8 +2804,8 @@ { "metadata": { "name": "sharingDashboardImage", - "resourceVersion": "1747090555040", - "creationTimestamp": "2025-05-12T22:55:55Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enables image sharing functionality for dashboards", @@ -2998,7 +2818,7 @@ { "metadata": { "name": "showDashboardValidationWarnings", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-10-14T13:51:05Z" }, "spec": { @@ -3010,11 +2830,8 @@ { "metadata": { "name": "skipTokenRotationIfRecent", - "resourceVersion": "1751872762065", - "creationTimestamp": "2025-06-03T06:59:40Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-07 07:19:22.065046 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-06-03T06:59:40Z" }, "spec": { "description": "Skip token rotation if it was already rotated less than 5 seconds ago", @@ -3028,7 +2845,7 @@ { "metadata": { "name": "sqlDatasourceDatabaseSelection", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-06-06T16:28:52Z" }, "spec": { @@ -3042,7 +2859,7 @@ { "metadata": { "name": "sqlExpressions", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-02-27T21:16:00Z" }, "spec": { @@ -3051,10 +2868,23 @@ "codeowner": "@grafana/grafana-datasources-core-services" } }, + { + "metadata": { + "name": "sqlExpressionsColumnAutoComplete", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enables column autocomplete for SQL Expressions", + "stage": "experimental", + "codeowner": "@grafana/datapro", + "frontend": true + } + }, { "metadata": { "name": "sseGroupByDatasource", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-09-07T20:02:07Z" }, "spec": { @@ -3063,25 +2893,10 @@ "codeowner": "@grafana/observability-metrics" } }, - { - "metadata": { - "name": "ssoSettingsApi", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-11-08T09:50:01Z", - "deletionTimestamp": "2025-07-02T14:16:57Z" - }, - "spec": { - "description": "Enables the SSO settings API and the OAuth configuration UIs in Grafana", - "stage": "GA", - "codeowner": "@grafana/identity-access-team", - "allowSelfServe": true, - "expression": "true" - } - }, { "metadata": { "name": "ssoSettingsLDAP", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-18T11:31:27Z" }, "spec": { @@ -3093,25 +2908,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "ssoSettingsSAML", - "resourceVersion": "1750434297879", - "creationTimestamp": "2024-03-14T11:04:45Z", - "deletionTimestamp": "2025-07-15T09:43:39Z" - }, - "spec": { - "description": "Use the new SSO Settings API to configure the SAML connector", - "stage": "GA", - "codeowner": "@grafana/identity-access-team", - "allowSelfServe": true, - "expression": "true" - } - }, { "metadata": { "name": "storage", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2022-03-17T17:19:23Z" }, "spec": { @@ -3123,7 +2923,7 @@ { "metadata": { "name": "tableNextGen", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-26T03:57:57Z" }, "spec": { @@ -3136,7 +2936,7 @@ { "metadata": { "name": "tableSharedCrosshair", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-12-13T09:33:14Z" }, "spec": { @@ -3149,11 +2949,8 @@ { "metadata": { "name": "tabularNumbers", - "resourceVersion": "1752078306230", - "creationTimestamp": "2025-06-24T11:52:03Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-07-09 16:25:06.230922635 +0000 UTC" - } + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-06-24T11:52:03Z" }, "spec": { "description": "Use fixed-width numbers globally in the UI", @@ -3165,7 +2962,7 @@ { "metadata": { "name": "teamHttpHeadersMimir", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-13T10:42:47Z" }, "spec": { @@ -3179,7 +2976,7 @@ { "metadata": { "name": "teamHttpHeadersTempo", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-05-22T19:13:31Z" }, "spec": { @@ -3191,7 +2988,7 @@ { "metadata": { "name": "templateVariablesUsesCombobox", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-31T09:53:13Z" }, "spec": { @@ -3204,8 +3001,8 @@ { "metadata": { "name": "tempoAlerting", - "resourceVersion": "1750158458662", - "creationTimestamp": "2025-06-17T11:07:38Z" + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" }, "spec": { "description": "Enables creating alerts from Tempo data source", @@ -3214,10 +3011,23 @@ "frontend": true } }, + { + "metadata": { + "name": "timeComparison", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enables time comparison option in supported panels", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, { "metadata": { "name": "timeRangeProvider", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-22T10:52:33Z" }, "spec": { @@ -3229,7 +3039,7 @@ { "metadata": { "name": "tlsMemcached", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-05-09T19:12:08Z" }, "spec": { @@ -3242,7 +3052,7 @@ { "metadata": { "name": "transformationsRedesign", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-07-12T16:35:49Z" }, "spec": { @@ -3257,7 +3067,7 @@ { "metadata": { "name": "unifiedHistory", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-13T10:41:18Z" }, "spec": { @@ -3270,7 +3080,7 @@ { "metadata": { "name": "unifiedNavbars", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-04-09T12:51:22Z" }, "spec": { @@ -3284,7 +3094,7 @@ { "metadata": { "name": "unifiedRequestLog", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2023-03-31T13:38:09Z" }, "spec": { @@ -3298,7 +3108,7 @@ { "metadata": { "name": "unifiedStorageBigObjectsSupport", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-10-17T10:18:29Z" }, "spec": { @@ -3310,7 +3120,7 @@ { "metadata": { "name": "unifiedStorageGrpcConnectionPool", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-21T13:24:54Z" }, "spec": { @@ -3324,7 +3134,7 @@ { "metadata": { "name": "unifiedStorageHistoryPruner", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-17T10:36:38Z" }, "spec": { @@ -3339,7 +3149,7 @@ { "metadata": { "name": "unifiedStorageSearch", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-30T19:46:14Z" }, "spec": { @@ -3350,10 +3160,24 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "unifiedStorageSearchDualReaderEnabled", + "resourceVersion": "1753448760331", + "creationTimestamp": "2025-07-25T13:06:00Z" + }, + "spec": { + "description": "Enable dual reader for unified storage search", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "unifiedStorageSearchPermissionFiltering", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-01-22T11:38:37Z" }, "spec": { @@ -3368,7 +3192,7 @@ { "metadata": { "name": "unifiedStorageSearchSprinkles", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-18T17:00:54Z" }, "spec": { @@ -3382,7 +3206,7 @@ { "metadata": { "name": "unifiedStorageSearchUI", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-12-19T18:21:48Z" }, "spec": { @@ -3396,7 +3220,7 @@ { "metadata": { "name": "useScopesNavigationEndpoint", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2025-03-31T15:20:00Z" }, "spec": { @@ -3411,7 +3235,7 @@ { "metadata": { "name": "useSessionStorageForRedirection", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-09-23T09:31:23Z" }, "spec": { @@ -3424,7 +3248,7 @@ { "metadata": { "name": "zanzana", - "resourceVersion": "1750434297879", + "resourceVersion": "1753448760331", "creationTimestamp": "2024-06-19T13:59:47Z" }, "spec": { @@ -3436,4 +3260,4 @@ } } ] -} +} \ No newline at end of file diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 68e570cdb6d..4690d6c4640 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -122,6 +122,7 @@ func ProvideService( userService, resourceClient, sorter, + features, ) unifiedStore := ProvideUnifiedStore(k8sHandler, userService, tracer) @@ -140,6 +141,7 @@ func ProvideService( userService, resourceClient, sorter, + features, ) srv.dashboardK8sClient = dashHandler } diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index 738e1e98f56..44bd735445f 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -199,7 +199,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) { tracer := noop.NewTracerProvider().Tracer("TestIntegrationFolderServiceViaUnifiedStorage") dashboardStore := dashboards.NewFakeDashboardStore(t) - k8sCli := client.NewK8sHandler(dualwrite.ProvideTestService(), request.GetNamespaceMapper(cfg), folderv1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil, sort.ProvideService()) + k8sCli := client.NewK8sHandler(dualwrite.ProvideTestService(), request.GetNamespaceMapper(cfg), folderv1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil, sort.ProvideService(), nil) unifiedStore := ProvideUnifiedStore(k8sCli, userService, tracer) ctx := context.Background() diff --git a/pkg/services/frontend/context_middleware.go b/pkg/services/frontend/context_middleware.go new file mode 100644 index 00000000000..5ef04ee95df --- /dev/null +++ b/pkg/services/frontend/context_middleware.go @@ -0,0 +1,41 @@ +package frontend + +import ( + "context" + "net/http" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/web" +) + +// Minimal copy of contextHandler.Middleware for frontend-service +// frontend-service doesn't handle authentication or know what signed in users are +func (s *frontendService) contextMiddleware() web.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + reqContext := &contextmodel.ReqContext{ + Context: web.FromContext(ctx), + Logger: log.New("context"), + } + + // inject ReqContext in the context + ctx = context.WithValue(ctx, ctxkey.Key{}, reqContext) + + // Set the context for the http.Request.Context + // This modifies both r and reqContext.Req since they point to the same value + *reqContext.Req = *reqContext.Req.WithContext(ctx) + + traceID := tracing.TraceIDFromContext(ctx, false) + if traceID != "" { + reqContext.Logger = reqContext.Logger.New("traceID", traceID) + } + + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index b79876dd4c5..0852a7a2147 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -9,11 +9,17 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/middleware/loggermw" + "github.com/grafana/grafana/pkg/middleware/requestmeta" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web" ) var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/frontend") @@ -22,14 +28,18 @@ type frontendService struct { *services.BasicService cfg *setting.Cfg httpServ *http.Server + features featuremgmt.FeatureToggles log log.Logger errChan chan error promGatherer prometheus.Gatherer + promRegister prometheus.Registerer + tracer trace.Tracer + license licensing.Licensing index *IndexProvider } -func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer, license licensing.Licensing) (*frontendService, error) { +func ProvideFrontendService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, promGatherer prometheus.Gatherer, promRegister prometheus.Registerer, license licensing.Licensing) (*frontendService, error) { index, err := NewIndexProvider(cfg, license) if err != nil { return nil, err @@ -37,8 +47,12 @@ func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer, s := &frontendService{ cfg: cfg, + features: features, log: log.New("frontend-server"), promGatherer: promGatherer, + promRegister: promRegister, + tracer: tracer, + license: license, index: index, } s.BasicService = services.NewBasicService(s.start, s.running, s.stop) @@ -65,6 +79,7 @@ func (s *frontendService) running(ctx context.Context) error { func (s *frontendService) stop(failureReason error) error { s.log.Info("stopping frontend server", "reason", failureReason) + if err := s.httpServ.Shutdown(context.Background()); err != nil { s.log.Error("failed to shutdown frontend server", "error", err) return err @@ -75,17 +90,48 @@ func (s *frontendService) stop(failureReason error) error { func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server { s.log.Info("starting frontend server", "addr", ":"+s.cfg.HTTPPort) - router := http.NewServeMux() - router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) - router.HandleFunc("/", s.index.HandleRequest) + // Use the same web.Mux as the main grafana server for consistency + middleware reuse + handler := web.New() + s.addMiddlewares(handler) + s.registerRoutes(handler) server := &http.Server{ // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) ReadHeaderTimeout: 5 * time.Second, Addr: ":" + s.cfg.HTTPPort, - Handler: router, + Handler: handler, BaseContext: func(_ net.Listener) context.Context { return ctx }, } return server } + +func (s *frontendService) routeGet(m *web.Mux, pattern string, h ...web.Handler) { + handlers := append([]web.Handler{middleware.ProvideRouteOperationName(pattern)}, h...) + m.Get(pattern, handlers...) +} + +// Apply the same middleware patterns as the main HTTP server +func (s *frontendService) addMiddlewares(m *web.Mux) { + loggermiddleware := loggermw.Provide(s.cfg, s.features) + + m.Use(requestmeta.SetupRequestMetadata()) + m.UseMiddleware(s.contextMiddleware()) + + m.Use(middleware.RequestTracing(s.tracer, middleware.TraceAllPaths)) + m.Use(middleware.RequestMetrics(s.features, s.cfg, s.promRegister)) + m.UseMiddleware(loggermiddleware.Middleware()) + + m.UseMiddleware(middleware.Recovery(s.cfg, s.license)) +} + +func (s *frontendService) registerRoutes(m *web.Mux) { + s.routeGet(m, "/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + + // Frontend service doesn't (yet?) serve any assets, so explicitly 404 + // them so we can get logs for them + s.routeGet(m, "/public/*", http.NotFound) + + // All other requests return index.html + s.routeGet(m, "/*", s.index.HandleRequest) +} diff --git a/pkg/services/frontend/frontend_service_test.go b/pkg/services/frontend/frontend_service_test.go new file mode 100644 index 00000000000..3af2812141a --- /dev/null +++ b/pkg/services/frontend/frontend_service_test.go @@ -0,0 +1,170 @@ +package frontend + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web" +) + +// Helper function to create a test service with minimal configuration +func createTestService(t *testing.T, cfg *setting.Cfg) *frontendService { + t.Helper() + + features := featuremgmt.WithFeatures() + license := &licensing.OSSLicensingService{} + + var promRegister prometheus.Registerer = prometheus.NewRegistry() + promGatherer := promRegister.(*prometheus.Registry) + + service, err := ProvideFrontendService(cfg, features, promGatherer, promRegister, license) + require.NoError(t, err) + + return service +} + +func TestFrontendService_ServerCreation(t *testing.T) { + t.Run("should create HTTP server with correct configuration", func(t *testing.T) { + publicDir := setupTestWebAssets(t) + cfg := &setting.Cfg{ + HTTPPort: "1234", + StaticRootPath: publicDir, + } + service := createTestService(t, cfg) + + ctx := context.Background() + server := service.newFrontendServer(ctx) + + assert.NotNil(t, server) + assert.Equal(t, ":1234", server.Addr) + assert.NotNil(t, server.Handler) + assert.NotNil(t, server.BaseContext) + }) +} + +func TestFrontendService_Routes(t *testing.T) { + publicDir := setupTestWebAssets(t) + cfg := &setting.Cfg{ + HTTPPort: "3000", + StaticRootPath: publicDir, + } + service := createTestService(t, cfg) + + // Create a test mux to verify route registration + mux := web.New() + service.addMiddlewares(mux) + service.registerRoutes(mux) + + t.Run("should handle frontend wildcard routes", func(t *testing.T) { + // Test that routes are registered by making requests + testCases := []struct { + path string + description string + }{ + // Metrics isn't registered in the test service? + {"/", "index route should return HTML"}, + {"/dashboards", "browse dashboards route should return HTML"}, + {"/d/de773f33s8qgwf/fep-homepage", "dashboard route should return HTML"}, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + req := httptest.NewRequest("GET", tc.path, nil) + recorder := httptest.NewRecorder() + + mux.ServeHTTP(recorder, req) + + assert.Equal(t, 200, recorder.Code) + assert.Contains(t, recorder.Body.String(), "
") + }) + } + }) + + t.Run("should handle assets 404 correctly", func(t *testing.T) { + req := httptest.NewRequest("GET", "/public/build/app.js", nil) + recorder := httptest.NewRecorder() + + mux.ServeHTTP(recorder, req) + + assert.Equal(t, 404, recorder.Code) + assert.Equal(t, "404 page not found", strings.TrimSpace(recorder.Body.String())) + }) + + t.Run("should return prometheus metrics", func(t *testing.T) { + testCounter := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "shrimp_count", + }) + err := service.promRegister.Register(testCounter) + require.NoError(t, err) + testCounter.Inc() + + req := httptest.NewRequest("GET", "/metrics", nil) + recorder := httptest.NewRecorder() + + mux.ServeHTTP(recorder, req) + + assert.Contains(t, recorder.Body.String(), "\nshrimp_count 1\n") + }) +} + +func TestFrontendService_Middleware(t *testing.T) { + publicDir := setupTestWebAssets(t) + cfg := &setting.Cfg{ + HTTPPort: "3000", + StaticRootPath: publicDir, + } + + t.Run("should register route prom metrics", func(t *testing.T) { + service := createTestService(t, cfg) + mux := web.New() + service.addMiddlewares(mux) + service.registerRoutes(mux) + + req := httptest.NewRequest("GET", "/dashboards", nil) + recorder := httptest.NewRecorder() + mux.ServeHTTP(recorder, req) + + req = httptest.NewRequest("GET", "/public/build/app.js", nil) + mux.ServeHTTP(recorder, req) + + req = httptest.NewRequest("GET", "/metrics", nil) + mux.ServeHTTP(recorder, req) + + metricsBody := recorder.Body.String() + assert.Contains(t, metricsBody, "# TYPE grafana_http_request_duration_seconds histogram") + assert.Contains(t, metricsBody, "grafana_http_request_duration_seconds_bucket{handler=\"public-assets\"") // assets 404 + assert.Contains(t, metricsBody, "grafana_http_request_duration_seconds_bucket{handler=\"/*\"") // index route + }) + + t.Run("should add context middleware", func(t *testing.T) { + service := createTestService(t, cfg) + mux := web.New() + service.addMiddlewares(mux) + + mux.Get("/test-route", func(w http.ResponseWriter, r *http.Request) { + ctx := contexthandler.FromContext(r.Context()) + assert.NotNil(t, ctx) + assert.NotNil(t, ctx.Context) + assert.NotNil(t, ctx.Logger) + + w.WriteHeader(200) + _, err := w.Write([]byte("ok")) + require.NoError(t, err) + }) + + req := httptest.NewRequest("GET", "/test-route", nil) + recorder := httptest.NewRecorder() + mux.ServeHTTP(recorder, req) + }) +} diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index b5e4c469798..2ece6114fa5 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -1,5 +1,5 @@ - + [[ if and .CSPEnabled .IsDevelopmentEnv ]] @@ -24,28 +24,133 @@ performance.mark('frontend_boot_css_time_seconds'); - + -
+
+ + +
+ + + + + +

Grafana is starting up...

+
+ +
+ + + + +

Error loading Grafana

+
+
+
- window.__grafana_load_failed = function(...args) { - console.error('Failed to load Grafana', ...args); - }; + [[range $asset := .Assets.JSFiles]] - + [[end]] diff --git a/pkg/services/frontend/index_test.go b/pkg/services/frontend/index_test.go new file mode 100644 index 00000000000..eaee0e8ff50 --- /dev/null +++ b/pkg/services/frontend/index_test.go @@ -0,0 +1,123 @@ +package frontend + +import ( + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web" +) + +// setupTestWebAssets creates a temporary directory with test assets manifest +func setupTestWebAssets(tb testing.TB) string { + tb.Helper() + + publicDir := tb.TempDir() + tb.Cleanup(func() { _ = os.RemoveAll(publicDir) }) + + // Create build directory + buildDir := filepath.Join(publicDir, "build") + err := os.MkdirAll(buildDir, 0750) + require.NoError(tb, err) + + // Create test assets manifest + manifest := `{ + "entrypoints": { + "app": { + "assets": { + "js": [ + "public/build/runtime.js", + "public/build/app.js" + ], + "css": ["public/build/grafana.app.css"] + } + }, + "swagger": { + "assets": { + "js": ["public/build/runtime.js", "public/build/swagger.js"], + "css": ["public/build/grafana.swagger.css"] + } + }, + "dark": { + "assets": { + "css": ["public/build/grafana.dark.css"] + } + }, + "light": { + "assets": { + "css": ["public/build/grafana.light.css"] + } + } + }, + "runtime.js": { + "src": "public/build/runtime.js", + "integrity": "sha256-test123" + }, + "app.js": { + "src": "public/build/app.js", + "integrity": "sha256-test456" + } + }` + + err = os.WriteFile(filepath.Join(buildDir, "assets-manifest.json"), []byte(manifest), 0644) + require.NoError(tb, err) + + return publicDir +} + +func TestFrontendService_WebAssets(t *testing.T) { + t.Run("should serve index with proper assets", func(t *testing.T) { + publicDir := setupTestWebAssets(t) + cfg := &setting.Cfg{ + HTTPPort: "3000", + StaticRootPath: publicDir, + Env: setting.Dev, // needs to be dev to bypass the cache + } + service := createTestService(t, cfg) + + mux := web.New() + service.addMiddlewares(mux) + service.registerRoutes(mux) + + // Test index route which should load web assets + req := httptest.NewRequest("GET", "/", nil) + recorder := httptest.NewRecorder() + + mux.ServeHTTP(recorder, req) + + assert.Equal(t, 200, recorder.Code) + assert.Contains(t, recorder.Header().Get("Content-Type"), "text/html") + + // The response should contain references to the assets + body := recorder.Body.String() + assert.Contains(t, body, "src=\"public/build/runtime.js\" type=\"text/javascript\"") + assert.Contains(t, body, "src=\"public/build/app.js\" type=\"text/javascript\"") + }) + + t.Run("should handle missing assets manifest gracefully", func(t *testing.T) { + cfg := &setting.Cfg{ + HTTPPort: "3000", + StaticRootPath: "/dev/null", // No build directory or manifest + Env: setting.Dev, // needs to be dev to bypass the cache + } + service := createTestService(t, cfg) + + mux := web.New() + service.addMiddlewares(mux) + service.registerRoutes(mux) + + // Test index route which should fail to load web assets + req := httptest.NewRequest("GET", "/", nil) + recorder := httptest.NewRecorder() + + mux.ServeHTTP(recorder, req) + + // Should return 500 due to missing assets manifest + assert.Equal(t, 500, recorder.Code) + }) +} diff --git a/pkg/services/mtdsclient/mt_datasource_client_builder.go b/pkg/services/mtdsclient/mt_datasource_client_builder.go new file mode 100644 index 00000000000..d5108aef4e2 --- /dev/null +++ b/pkg/services/mtdsclient/mt_datasource_client_builder.go @@ -0,0 +1,86 @@ +package mtdsclient + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" +) + +type MTDatasourceClientBuilder interface { + BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool) +} + +type nullBuilder struct{} + +func (m *nullBuilder) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool) { + return nil, false +} + +// we use this noop for st flows +func NewNullMTDatasourceClientBuilder() MTDatasourceClientBuilder { + return &nullBuilder{} +} + +type MtDatasourceClientBuilderWithClientSupplier struct { + clientSupplier clientapi.DataSourceClientSupplier + ctx context.Context + headers map[string]string + instanceConfig clientapi.InstanceConfigurationSettings + logger log.Logger +} + +func (b *MtDatasourceClientBuilderWithClientSupplier) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool) { + dsClient, err := b.clientSupplier.GetDataSourceClient( + b.ctx, + v0alpha1.DataSourceRef{ + Type: pluginId, + UID: uid, + }, + b.headers, + b.instanceConfig, + ) + if err != nil { + b.logger.Debug("failed to get mt ds client", "error", err) + return nil, false + } + return dsClient, true +} + +// TODO: I think we might be able to refactor this to just use the client supplier directly +func NewMtDatasourceClientBuilderWithClientSupplier( + clientSupplier clientapi.DataSourceClientSupplier, + ctx context.Context, + headers map[string]string, + instanceConfig clientapi.InstanceConfigurationSettings, + logger log.Logger, +) MTDatasourceClientBuilder { + return &MtDatasourceClientBuilderWithClientSupplier{ + clientSupplier: clientSupplier, + ctx: ctx, + headers: headers, + instanceConfig: instanceConfig, + logger: logger, + } +} + +func NewTestMTDSClientBuilder(isMultiTenant bool, mockClient clientapi.QueryDataClient) MTDatasourceClientBuilder { + return &testBuilder{ + mockClient: mockClient, + isMultitenant: isMultiTenant, + } +} + +type testBuilder struct { + mockClient clientapi.QueryDataClient + isMultitenant bool +} + +func (b *testBuilder) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool) { + if !b.isMultitenant { + return nil, false + } + + return b.mockClient, true +} diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go index 7478783da62..35d3da6d72a 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus_test.go +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -291,6 +291,30 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { } }) + t.Run("with empty rule group name should return 400", func(t *testing.T) { + srv, _, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + + emptyNameGroup := apimodels.PrometheusRuleGroup{ + Name: "", + Interval: prommodel.Duration(1 * time.Minute), + Rules: []apimodels.PrometheusRule{ + { + Alert: "TestAlert", + Expr: "up == 0", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + }, + }, + } + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", emptyNameGroup) + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "rule group name must not be empty") + }) + t.Run("with valid request should return 202", func(t *testing.T) { srv, _, _ := createConvertPrometheusSrv(t) rc := createRequestCtx() @@ -1567,7 +1591,18 @@ func TestRouteConvertPrometheusPostAlertmanagerConfig(t *testing.T) { ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI) srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft)) - amCfg := apimodels.AlertmanagerUserConfig{} + amCfg := apimodels.AlertmanagerUserConfig{ + AlertmanagerConfig: `{ + "route": { + "receiver": "default" + }, + "receivers": [ + { + "name": "default" + } + ] + }`, + } response := srv.RouteConvertPrometheusPostAlertmanagerConfig(rc, amCfg) require.Equal(t, http.StatusAccepted, response.Status()) @@ -1585,6 +1620,26 @@ func TestRouteConvertPrometheusPostAlertmanagerConfig(t *testing.T) { require.Equal(t, http.StatusBadRequest, response.Status()) require.Contains(t, string(response.Body()), "format should be 'key=value,key2=value2'") }) + + t.Run("should return error when alertmanager config has empty route", func(t *testing.T) { + rc := createRequestCtx() + rc.Req.Header.Set(configIdentifierHeader, identifier) + rc.Req.Header.Set(mergeMatchersHeader, "env=prod") + + amCfg := apimodels.AlertmanagerUserConfig{ + AlertmanagerConfig: `{ + "receivers": [ + { + "name": "default" + } + ] + }`, + } + response := srv.RouteConvertPrometheusPostAlertmanagerConfig(rc, amCfg) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "failed to parse alertmanager config") + }) } func TestRouteConvertPrometheusGetAlertmanagerConfig(t *testing.T) { diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 00f57988df9..286dd7e4404 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -1,7 +1,9 @@ package api import ( + "bytes" "context" + "embed" "encoding/json" "fmt" "net/http" @@ -12,6 +14,7 @@ import ( "testing" "time" + alertingNotify "github.com/grafana/alerting/notify" prometheus "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/timeinterval" @@ -57,6 +60,9 @@ import ( "github.com/grafana/grafana/pkg/web" ) +//go:embed test-data/receiver-exports/* +var receiverExportResponses embed.FS + func TestMain(m *testing.M) { testsuite.Run(m) } @@ -1896,6 +1902,112 @@ func TestIntegrationProvisioningApiContactPointExport(t *testing.T) { }) } +func TestApiContactPointExportSnapshot(t *testing.T) { + // This test should fail whenever the export of a contact point changes. If the change is expected, update + // the corresponding test response file(s) in test-data/receiver-exports/* + type testcase struct { + name string + receiver models.Receiver + redacted bool + exportType string + } + runTestCase := func(t *testing.T, tc testcase) { + postableReceiver, err := legacy_storage.ReceiverToPostableApiReceiver(&tc.receiver) + require.NoError(t, err) + postable := definitions.PostableUserConfig{ + AlertmanagerConfig: definitions.PostableApiAlertingConfig{ + Config: definitions.Config{ + Route: &definitions.Route{ + Receiver: postableReceiver.Name, + }, + }, + Receivers: []*definitions.PostableApiReceiver{postableReceiver}, + }, + } + + amConfig, err := json.Marshal(postable) + require.NoError(t, err) + + env := createTestEnv(t, string(amConfig)) + env.ac.Callback = func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { + return true, nil + } + sut := createProvisioningSrvSutFromEnv(t, &env) + rc := createTestRequestCtx() + + switch tc.exportType { + case "yaml": + rc.Req.Header.Add("Accept", "application/yaml") + case "json": + rc.Req.Header.Add("Accept", "application/json") + case "hcl": + rc.Req.Form.Add("format", "hcl") + default: + t.Fatalf("unknown export type %q", tc.exportType) + } + + if tc.redacted { + rc.Req.Form.Set("decrypt", "false") + } else { + rc.Req.Form.Set("decrypt", "true") + } + + response := sut.RouteGetContactPointsExport(&rc) + require.Equalf(t, 200, response.Status(), "expected 200, got %d, body: %q", response.Status(), response.Body()) + + actualBody := response.Body() + if tc.exportType == "json" { + // Indent the JSON for easier comparison. + // This isn't strictly necessary, but it makes the test output more readable. + out := new(bytes.Buffer) + err = json.Indent(out, actualBody, "", " ") + require.NoError(t, err) + actualBody = out.Bytes() + } + + p := path.Join("test-data", "receiver-exports", "redacted") + if !tc.redacted { + p = path.Join("test-data", "receiver-exports", "unredacted") + } + p = path.Join(p, fmt.Sprintf("%s.%s", tc.name, tc.exportType)) + + // To update these files: os.WriteFile(path.Join(p), actualBody, 0644) + + exportRaw, err := receiverExportResponses.ReadFile(p) + require.NoError(t, err) + require.Equal(t, string(exportRaw), string(actualBody)) + } + + t.Run("contact point export for all known configs", func(t *testing.T) { + allIntegrationsName := "all-integrations" + for _, exportType := range []string{"yaml", "json", "hcl"} { + t.Run(fmt.Sprintf("exportType=%s", exportType), func(t *testing.T) { + for _, redacted := range []bool{true, false} { + t.Run(fmt.Sprintf("redacted=%t", redacted), func(t *testing.T) { + allIntegrations := make([]models.Integration, 0, len(alertingNotify.AllKnownConfigsForTesting)) + for integrationType := range alertingNotify.AllKnownConfigsForTesting { + integration := models.IntegrationGen( + models.IntegrationMuts.WithName(allIntegrationsName), + models.IntegrationMuts.WithUID(fmt.Sprintf("%s-uid", integrationType)), + models.IntegrationMuts.WithValidConfig(integrationType), + )() + integration.DisableResolveMessage = redacted + allIntegrations = append(allIntegrations, integration) + } + receiver := models.ReceiverGen(models.ReceiverMuts.WithName(allIntegrationsName), models.ReceiverMuts.WithIntegrations(allIntegrations...))() + runTestCase(t, testcase{ + name: allIntegrationsName, + receiver: receiver, + redacted: redacted, + exportType: exportType, + }) + }) + } + }) + } + }) +} + // testEnvironment binds together common dependencies for testing alerting APIs. type testEnvironment struct { secrets secrets.Service diff --git a/pkg/services/ngalert/api/compat_contact_points.go b/pkg/services/ngalert/api/compat_contact_points.go index 1299af0bb64..e18b5c0a74a 100644 --- a/pkg/services/ngalert/api/compat_contact_points.go +++ b/pkg/services/ngalert/api/compat_contact_points.go @@ -419,6 +419,12 @@ func (c contactPointsExtension) UpdateStructDescriptor(structDescriptor *jsonite desc.Decoder = codec desc.Encoder = codec } + if structDescriptor.Type == reflect2.TypeOf(definitions.JiraIntegration{}) { + bind := structDescriptor.GetField("Fields") + codec := &mapToJSONStringCodec{} + bind.Decoder = codec + bind.Encoder = codec + } } type emailAddressCodec struct{} @@ -494,3 +500,55 @@ func (d *numberAsStringCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator } *((*(*int64))(ptr)) = &value } + +type mapToJSONStringCodec struct{} + +func (d *mapToJSONStringCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + var str string + switch iter.WhatIsNext() { + case jsoniter.ObjectValue: + var raw map[string]any + iter.ReadVal(&raw) + b, err := json.Marshal(raw) + if err != nil { + iter.ReportError("mapToJSONStringCodec.Decode", err.Error()) + return + } + str = string(b) + case jsoniter.NilValue: + iter.ReadNil() + *(**string)(ptr) = nil + return + default: + iter.ReportError("mapToJSONStringCodec.Decode", "unsupported input type") + return + } + // Allocate a new string and set the pointer. + newStr := str + *(**string)(ptr) = &newStr +} + +// IsEmpty is used by Encoder to determine if the field is empty. +func (d *mapToJSONStringCodec) IsEmpty(ptr unsafe.Pointer) bool { + strPtr := *(**string)(ptr) + return strPtr == nil || *strPtr == "" +} + +// This method is not used in production code, but is required by a test that ensure marshalling and unmarshalling does not change the value. +func (d *mapToJSONStringCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + strPtr := *(**string)(ptr) + if strPtr == nil { + stream.WriteNil() + return + } + + // Validate the string contains valid JSON + var raw any + if err := json.Unmarshal([]byte(*strPtr), &raw); err != nil { + stream.Error = fmt.Errorf("invalid JSON in *string field: %w", err) + return + } + + // Write the parsed value as native JSON (object, array, etc.) + stream.WriteVal(raw) +} diff --git a/pkg/services/ngalert/api/compat_contact_points_test.go b/pkg/services/ngalert/api/compat_contact_points_test.go index d20914633c8..bda8336b0bf 100644 --- a/pkg/services/ngalert/api/compat_contact_points_test.go +++ b/pkg/services/ngalert/api/compat_contact_points_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" + "github.com/grafana/grafana/pkg/util" ) // Test that conversion notify.APIReceiver -> definitions.ContactPoint -> notify.APIReceiver does not lose data @@ -226,4 +227,87 @@ func TestContactPointFromContactPointExports(t *testing.T) { require.Nil(t, result.Mqtt[1].QoS) require.Nil(t, result.Mqtt[2].QoS) }) + + t.Run("jira with various fields values as string", func(t *testing.T) { + testcases := []struct { + name string + input definitions.RawMessage + expected *string + expectedErr bool + }{ + { + name: "standard map[string]string", + input: definitions.RawMessage(`{ "fields" : {"test-data" : "test-value"} }`), + expected: util.Pointer(`{"test-data":"test-value"}`), + }, + { + name: "map[string]int", + input: definitions.RawMessage(`{ "fields" : {"test-data" : 42} }`), + expected: util.Pointer(`{"test-data":42}`), + }, + { + name: "map[string]interface{} with null value", + input: definitions.RawMessage(`{ "fields" : {"test-data" : null} }`), + expected: util.Pointer(`{"test-data":null}`), + }, + { + name: "null fields", + input: definitions.RawMessage(`{ "fields" : null }`), + expected: nil, + }, + { + name: "empty map", + input: definitions.RawMessage(`{ "fields" : {} }`), + expected: util.Pointer(`{}`), + }, + { + name: "nested map", + input: definitions.RawMessage(`{ "fields" : {"test-data" : {"test-data-nested" : "test-value-nested"}} }`), + expected: util.Pointer(`{"test-data":{"test-data-nested":"test-value-nested"}}`), + }, + { + name: "nested slice", + input: definitions.RawMessage(`{ "fields" : {"test-data" : ["slice1", "slice2"]} }`), + expected: util.Pointer(`{"test-data":["slice1","slice2"]}`), + }, + { + name: "string value", + input: definitions.RawMessage(`{ "fields" : "some string" }`), + expectedErr: true, + }, + { + name: "slice", + input: definitions.RawMessage(`{ "fields" : ["slice1", "slice2"} }`), + expectedErr: true, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + export := definitions.ContactPointExport{ + Name: "test", + Receivers: []definitions.ReceiverExport{ + { + Type: "jira", + Settings: tc.input, + }, + }, + } + + result, err := ContactPointFromContactPointExport(export) + if tc.expectedErr { + require.Error(t, err, "Expected error for input: %s", tc.input) + return + } else { + require.NoError(t, err, "Unexpected error for input: %s", tc.input) + } + require.Len(t, result.Jira, 1) + + if tc.expected == nil { + require.Nil(t, result.Jira[0].Fields) + } else { + require.Equal(t, *tc.expected, *result.Jira[0].Fields) + } + }) + } + }) } diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.hcl b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.hcl new file mode 100644 index 00000000000..32a9210f42a --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.hcl @@ -0,0 +1,327 @@ +resource "grafana_contact_point" "contact_point_2b661702215368fe" { + name = "all-integrations" + + alertmanager { + disable_resolve_message = true + url = "https://alertmanager-01.com" + basic_auth_user = "grafana" + basic_auth_password = "[REDACTED]" + } + + dingding { + disable_resolve_message = true + url = "[REDACTED]" + message_type = "actionCard" + title = "Alerts firing: {{ len .Alerts.Firing }}" + message = "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved" + } + + discord { + disable_resolve_message = true + url = "[REDACTED]" + title = "test-title" + message = "test-message" + avatar_url = "http://avatar" + use_discord_username = true + } + + email { + disable_resolve_message = true + addresses = ["test@grafana.com"] + single_email = true + message = "test-message" + subject = "test-subject" + } + + googlechat { + disable_resolve_message = true + url = "[REDACTED]" + title = "test-title" + message = "test-message" + } + + jira { + disable_resolve_message = true + api_url = "http://localhost" + project = "Test Project" + issue_type = "Test Issue Type" + summary = "Test Summary" + description = "Test Description" + labels = ["Test Label", "Test Label 2"] + priority = "Test Priority" + reopen_transition = "Test Reopen Transition" + resolve_transition = "Test Resolve Transition" + wont_fix_resolution = "Test Won't Fix Resolution" + reopen_duration = "1m" + dedup_key_field = "10000" + fields = "{\"test-field\":\"test-value\"}" + user = "[REDACTED]" + password = "[REDACTED]" + } + + kafka { + disable_resolve_message = true + rest_proxy_url = "http://localhost/" + topic = "test-topic" + description = "test-description" + details = "test-details" + username = "test-user" + password = "[REDACTED]" + api_version = "v2" + cluster_id = "12345" + } + + line { + disable_resolve_message = true + token = "[REDACTED]" + title = "test-title" + description = "test-description" + } + + mqtt { + disable_resolve_message = true + broker_url = "tcp://localhost:1883" + client_id = "grafana-test-client-id" + topic = "grafana/alerts" + message_format = "json" + username = "test-username" + password = "[REDACTED]" + qos = 0 + retain = false + + tls_config { + insecure_skip_verify = false + ca_certificate = "[REDACTED]" + client_certificate = "[REDACTED]" + client_key = "[REDACTED]" + } + } + + opsgenie { + disable_resolve_message = true + api_key = "[REDACTED]" + url = "http://localhost" + message = "test-message" + description = "test-description" + auto_close = false + override_priority = false + send_tags_as = "both" + + responders { + id = "test-id" + type = "team" + } + responders { + username = "test-user" + type = "user" + } + responders { + name = "test-schedule" + type = "schedule" + } + } + + pagerduty { + disable_resolve_message = true + integration_key = "[REDACTED]" + severity = "test-severity" + class = "test-class" + component = "test-component" + group = "test-group" + summary = "test-summary" + source = "test-source" + client = "test-client" + client_url = "http://localhost/test-client-url" + url = "http://localhost/test-api-url" + } + + oncall { + disable_resolve_message = true + url = "http://localhost" + http_method = "PUT" + max_alerts = 2 + authorization_scheme = "basic" + basic_auth_user = "test-user" + basic_auth_password = "[REDACTED]" + title = "test-title" + message = "test-message" + } + + pushover { + disable_resolve_message = true + user_key = "[REDACTED]" + api_token = "[REDACTED]" + priority = 1 + ok_priority = 2 + retry = 555 + expire = 333 + device = "test-device" + sound = "test-sound" + ok_sound = "test-ok-sound" + title = "test-title" + message = "test-message" + upload_image = false + } + + sensugo { + disable_resolve_message = true + url = "http://localhost" + api_key = "[REDACTED]" + entity = "test-entity" + check = "test-check" + namespace = "test-namespace" + handler = "test-handler" + message = "test-message" + } + + slack { + disable_resolve_message = true + endpoint_url = "http://localhost/endpoint_url" + url = "[REDACTED]" + token = "[REDACTED]" + recipient = "test-recipient" + text = "test-text" + title = "test-title" + username = "test-username" + icon_emoji = "test-icon" + icon_url = "http://localhost/icon_url" + mention_channel = "channel" + mention_users = "test-mentionUsers" + mention_groups = "test-mentionGroups" + color = "test-color" + } + + sns { + disable_resolve_message = true + api_url = "https://sns.us-east-1.amazonaws.com" + + sigv4 { + region = "us-east-1" + access_key = "[REDACTED]" + secret_key = "[REDACTED]" + profile = "default" + role_arn = "arn:aws:iam:us-east-1:0123456789:role/my-role" + } + + topic_arn = "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + phone_number = "123-456-7890" + target_arn = "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + subject = "subject" + message = "message" + attributes = { + attr1 = "val1" + } + } + + teams { + disable_resolve_message = true + url = "http://localhost" + message = "test-message" + title = "test-title" + section_title = "test-second-title" + } + + telegram { + disable_resolve_message = true + token = "[REDACTED]" + chat_id = "12345678" + message_thread_id = "13579" + message = "test-message" + parse_mode = "html" + disable_web_page_preview = true + protect_content = true + disable_notifications = true + } + + threema { + disable_resolve_message = true + gateway_id = "*1234567" + recipient_id = "*1234567" + api_secret = "[REDACTED]" + title = "test-title" + description = "test-description" + } + + victorops { + disable_resolve_message = true + url = "[REDACTED]" + message_type = "test-messagetype" + title = "test-title" + description = "test-description" + } + + webhook { + disable_resolve_message = true + url = "http://localhost" + http_method = "PUT" + max_alerts = 2 + authorization_scheme = "basic" + basic_auth_user = "test-user" + basic_auth_password = "[REDACTED]" + title = "test-title" + message = "test-message" + + tlsConfig { + insecure_skip_verify = false + ca_certificate = "[REDACTED]" + client_certificate = "[REDACTED]" + client_key = "[REDACTED]" + } + + hmacConfig { + secret = "[REDACTED]" + header = "X-Grafana-Alerting-Signature" + timestamp_header = "X-Grafana-Alerting-Timestamp" + } + + http_config { + + oauth2 { + client_id = "test-client-id" + client_secret = "[REDACTED]" + token_url = "https://localhost/auth/token" + scopes = ["scope1", "scope2"] + endpoint_params = { + param1 = "value1" + param2 = "value2" + } + + tls_config { + insecure_skip_verify = false + ca_certificate = "[REDACTED]" + client_certificate = "[REDACTED]" + client_key = "[REDACTED]" + } + + proxy_config { + proxy_url = "http://localproxy:8080" + no_proxy = "localhost" + proxy_from_environment = false + proxy_connect_header = { + X-Proxy-Header = "proxy-value" + } + } + } + } + } + + wecom { + disable_resolve_message = true + url = "[REDACTED]" + secret = "[REDACTED]" + agent_id = "test-agent_id" + corp_id = "test-corp_id" + message = "test-message" + title = "test-title" + msg_type = "markdown" + to_user = "test-touser" + } + + webex { + disable_resolve_message = true + token = "[REDACTED]" + api_url = "http://localhost" + message = "test-message" + room_id = "test-room-id" + } +} diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.json b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.json new file mode 100644 index 00000000000..1857db2807f --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.json @@ -0,0 +1,898 @@ +{ + "apiVersion": 1, + "contactPoints": [ + { + "orgId": 1, + "name": "all-integrations", + "receivers": [ + { + "uid": "dingding-uid", + "type": "dingding", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved", + "msgType": "actionCard", + "title": "Alerts firing: {{ len .Alerts.Firing }}", + "url": "[REDACTED]" + }, + "disableResolveMessage": true + }, + { + "uid": "discord-uid", + "type": "discord", + "settings": { + "avatar_url": "http://avatar", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "title": "test-title", + "url": "[REDACTED]", + "use_discord_username": true + }, + "disableResolveMessage": true + }, + { + "uid": "email-uid", + "type": "email", + "settings": { + "addresses": "test@grafana.com", + "message": "test-message", + "singleEmail": true, + "subject": "test-subject" + }, + "disableResolveMessage": true + }, + { + "uid": "googlechat-uid", + "type": "googlechat", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "title": "test-title", + "url": "[REDACTED]" + }, + "disableResolveMessage": true + }, + { + "uid": "jira-uid", + "type": "jira", + "settings": { + "api_url": "http://localhost", + "dedup_key_field": "10000", + "description": "Test Description", + "fields": { + "test-field": "test-value" + }, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "issue_type": "Test Issue Type", + "labels": [ + "Test Label", + "Test Label 2" + ], + "password": "[REDACTED]", + "priority": "Test Priority", + "project": "Test Project", + "reopen_duration": "1m", + "reopen_transition": "Test Reopen Transition", + "resolve_transition": "Test Resolve Transition", + "summary": "Test Summary", + "user": "[REDACTED]", + "wont_fix_resolution": "Test Won't Fix Resolution" + }, + "disableResolveMessage": true + }, + { + "uid": "kafka-uid", + "type": "kafka", + "settings": { + "apiVersion": "v2", + "description": "test-description", + "details": "test-details", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "kafkaClusterId": "12345", + "kafkaRestProxy": "http://localhost/", + "kafkaTopic": "test-topic", + "password": "[REDACTED]", + "username": "test-user" + }, + "disableResolveMessage": true + }, + { + "uid": "line-uid", + "type": "LINE", + "settings": { + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "title": "test-title", + "token": "[REDACTED]" + }, + "disableResolveMessage": true + }, + { + "uid": "mqtt-uid", + "type": "mqtt", + "settings": { + "brokerUrl": "tcp://localhost:1883", + "clientId": "grafana-test-client-id", + "messageFormat": "json", + "password": "[REDACTED]", + "qos": "0", + "retain": false, + "tlsConfig": { + "caCertificate": "[REDACTED]", + "clientCertificate": "[REDACTED]", + "clientKey": "[REDACTED]", + "insecureSkipVerify": false + }, + "topic": "grafana/alerts", + "username": "test-username" + }, + "disableResolveMessage": true + }, + { + "uid": "oncall-uid", + "type": "oncall", + "settings": { + "authorization_scheme": "basic", + "httpMethod": "PUT", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "maxAlerts": "2", + "message": "test-message", + "password": "[REDACTED]", + "title": "test-title", + "url": "http://localhost", + "username": "test-user" + }, + "disableResolveMessage": true + }, + { + "uid": "opsgenie-uid", + "type": "opsgenie", + "settings": { + "apiKey": "[REDACTED]", + "apiUrl": "http://localhost", + "autoClose": false, + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "overridePriority": false, + "responders": [ + { + "id": "test-id", + "type": "team" + }, + { + "type": "user", + "username": "test-user" + }, + { + "name": "test-schedule", + "type": "schedule" + } + ], + "sendTagsAs": "both" + }, + "disableResolveMessage": true + }, + { + "uid": "pagerduty-uid", + "type": "pagerduty", + "settings": { + "class": "test-class", + "client": "test-client", + "client_url": "http://localhost/test-client-url", + "component": "test-component", + "group": "test-group", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "integrationKey": "[REDACTED]", + "severity": "test-severity", + "source": "test-source", + "summary": "test-summary", + "url": "http://localhost/test-api-url" + }, + "disableResolveMessage": true + }, + { + "uid": "prometheus-alertmanager-uid", + "type": "prometheus-alertmanager", + "settings": { + "basicAuthPassword": "[REDACTED]", + "basicAuthUser": "grafana", + "url": "https://alertmanager-01.com" + }, + "disableResolveMessage": true + }, + { + "uid": "pushover-uid", + "type": "pushover", + "settings": { + "apiToken": "[REDACTED]", + "device": "test-device", + "expire": 333, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "okPriority": 2, + "okSound": "test-ok-sound", + "priority": 1, + "retry": 555, + "sound": "test-sound", + "title": "test-title", + "uploadImage": false, + "userKey": "[REDACTED]" + }, + "disableResolveMessage": true + }, + { + "uid": "sensugo-uid", + "type": "sensugo", + "settings": { + "apikey": "[REDACTED]", + "check": "test-check", + "entity": "test-entity", + "handler": "test-handler", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "namespace": "test-namespace", + "url": "http://localhost" + }, + "disableResolveMessage": true + }, + { + "uid": "slack-uid", + "type": "slack", + "settings": { + "color": "test-color", + "endpointUrl": "http://localhost/endpoint_url", + "icon_emoji": "test-icon", + "icon_url": "http://localhost/icon_url", + "mentionChannel": "channel", + "mentionGroups": "test-mentionGroups", + "mentionUsers": "test-mentionUsers", + "recipient": "test-recipient", + "text": "test-text", + "title": "test-title", + "token": "[REDACTED]", + "url": "[REDACTED]", + "username": "test-username" + }, + "disableResolveMessage": true + }, + { + "uid": "sns-uid", + "type": "sns", + "settings": { + "api_url": "https://sns.us-east-1.amazonaws.com", + "attributes": { + "attr1": "val1" + }, + "message": "message", + "phone_number": "123-456-7890", + "sigv4": { + "access_key": "[REDACTED]", + "profile": "default", + "region": "us-east-1", + "role_arn": "arn:aws:iam:us-east-1:0123456789:role/my-role", + "secret_key": "[REDACTED]" + }, + "subject": "subject", + "target_arn": "arn:aws:sns:us-east-1:0123456789:SNSTopicName", + "topic_arn": "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + }, + "disableResolveMessage": true + }, + { + "uid": "teams-uid", + "type": "teams", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "sectiontitle": "test-second-title", + "title": "test-title", + "url": "http://localhost" + }, + "disableResolveMessage": true + }, + { + "uid": "telegram-uid", + "type": "telegram", + "settings": { + "bottoken": "[REDACTED]", + "chatid": "12345678", + "disable_notifications": true, + "disable_web_page_preview": true, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "message_thread_id": "13579", + "parse_mode": "html", + "protect_content": true + }, + "disableResolveMessage": true + }, + { + "uid": "threema-uid", + "type": "threema", + "settings": { + "api_secret": "[REDACTED]", + "description": "test-description", + "gateway_id": "*1234567", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "recipient_id": "*1234567", + "title": "test-title" + }, + "disableResolveMessage": true + }, + { + "uid": "victorops-uid", + "type": "victorops", + "settings": { + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "messageType": "test-messagetype", + "title": "test-title", + "url": "[REDACTED]" + }, + "disableResolveMessage": true + }, + { + "uid": "webex-uid", + "type": "webex", + "settings": { + "api_url": "http://localhost", + "bot_token": "[REDACTED]", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "room_id": "test-room-id" + }, + "disableResolveMessage": true + }, + { + "uid": "webhook-uid", + "type": "webhook", + "settings": { + "authorization_scheme": "basic", + "hmacConfig": { + "header": "X-Grafana-Alerting-Signature", + "secret": "[REDACTED]", + "timestampHeader": "X-Grafana-Alerting-Timestamp" + }, + "httpMethod": "PUT", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "[REDACTED]", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "[REDACTED]", + "clientCertificate": "[REDACTED]", + "clientKey": "[REDACTED]", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "maxAlerts": "2", + "message": "test-message", + "password": "[REDACTED]", + "title": "test-title", + "tlsConfig": { + "caCertificate": "[REDACTED]", + "clientCertificate": "[REDACTED]", + "clientKey": "[REDACTED]", + "insecureSkipVerify": false + }, + "url": "http://localhost", + "username": "test-user" + }, + "disableResolveMessage": true + }, + { + "uid": "wecom-uid", + "type": "wecom", + "settings": { + "agent_id": "test-agent_id", + "corp_id": "test-corp_id", + "endpointUrl": "http://localhost/test-endpointUrl", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "msgtype": "markdown", + "secret": "[REDACTED]", + "title": "test-title", + "touser": "test-touser", + "url": "[REDACTED]" + }, + "disableResolveMessage": true + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.yaml b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.yaml new file mode 100644 index 00000000000..83e31ec0203 --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/redacted/all-integrations.yaml @@ -0,0 +1,1057 @@ +apiVersion: 1 +contactPoints: + - orgId: 1 + name: all-integrations + receivers: + - uid: dingding-uid + type: dingding + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: '{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved' + msgType: actionCard + title: 'Alerts firing: {{ len .Alerts.Firing }}' + url: '[REDACTED]' + disableResolveMessage: true + - uid: discord-uid + type: discord + settings: + avatar_url: http://avatar + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + title: test-title + url: '[REDACTED]' + use_discord_username: true + disableResolveMessage: true + - uid: email-uid + type: email + settings: + addresses: test@grafana.com + message: test-message + singleEmail: true + subject: test-subject + disableResolveMessage: true + - uid: googlechat-uid + type: googlechat + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + title: test-title + url: '[REDACTED]' + disableResolveMessage: true + - uid: jira-uid + type: jira + settings: + api_url: http://localhost + dedup_key_field: "10000" + description: Test Description + fields: + test-field: test-value + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + issue_type: Test Issue Type + labels: + - Test Label + - Test Label 2 + password: '[REDACTED]' + priority: Test Priority + project: Test Project + reopen_duration: 1m + reopen_transition: Test Reopen Transition + resolve_transition: Test Resolve Transition + summary: Test Summary + user: '[REDACTED]' + wont_fix_resolution: Test Won't Fix Resolution + disableResolveMessage: true + - uid: kafka-uid + type: kafka + settings: + apiVersion: v2 + description: test-description + details: test-details + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + kafkaClusterId: "12345" + kafkaRestProxy: http://localhost/ + kafkaTopic: test-topic + password: '[REDACTED]' + username: test-user + disableResolveMessage: true + - uid: line-uid + type: LINE + settings: + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + title: test-title + token: '[REDACTED]' + disableResolveMessage: true + - uid: mqtt-uid + type: mqtt + settings: + brokerUrl: tcp://localhost:1883 + clientId: grafana-test-client-id + messageFormat: json + password: '[REDACTED]' + qos: "0" + retain: false + tlsConfig: + caCertificate: '[REDACTED]' + clientCertificate: '[REDACTED]' + clientKey: '[REDACTED]' + insecureSkipVerify: false + topic: grafana/alerts + username: test-username + disableResolveMessage: true + - uid: oncall-uid + type: oncall + settings: + authorization_scheme: basic + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + httpMethod: PUT + maxAlerts: "2" + message: test-message + password: '[REDACTED]' + title: test-title + url: http://localhost + username: test-user + disableResolveMessage: true + - uid: opsgenie-uid + type: opsgenie + settings: + apiKey: '[REDACTED]' + apiUrl: http://localhost + autoClose: false + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + overridePriority: false + responders: + - id: test-id + type: team + - type: user + username: test-user + - name: test-schedule + type: schedule + sendTagsAs: both + disableResolveMessage: true + - uid: pagerduty-uid + type: pagerduty + settings: + class: test-class + client: test-client + client_url: http://localhost/test-client-url + component: test-component + group: test-group + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + integrationKey: '[REDACTED]' + severity: test-severity + source: test-source + summary: test-summary + url: http://localhost/test-api-url + disableResolveMessage: true + - uid: prometheus-alertmanager-uid + type: prometheus-alertmanager + settings: + basicAuthPassword: '[REDACTED]' + basicAuthUser: grafana + url: https://alertmanager-01.com + disableResolveMessage: true + - uid: pushover-uid + type: pushover + settings: + apiToken: '[REDACTED]' + device: test-device + expire: 333 + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + okPriority: 2 + okSound: test-ok-sound + priority: 1 + retry: 555 + sound: test-sound + title: test-title + uploadImage: false + userKey: '[REDACTED]' + disableResolveMessage: true + - uid: sensugo-uid + type: sensugo + settings: + apikey: '[REDACTED]' + check: test-check + entity: test-entity + handler: test-handler + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + namespace: test-namespace + url: http://localhost + disableResolveMessage: true + - uid: slack-uid + type: slack + settings: + color: test-color + endpointUrl: http://localhost/endpoint_url + icon_emoji: test-icon + icon_url: http://localhost/icon_url + mentionChannel: channel + mentionGroups: test-mentionGroups + mentionUsers: test-mentionUsers + recipient: test-recipient + text: test-text + title: test-title + token: '[REDACTED]' + url: '[REDACTED]' + username: test-username + disableResolveMessage: true + - uid: sns-uid + type: sns + settings: + api_url: https://sns.us-east-1.amazonaws.com + attributes: + attr1: val1 + message: message + phone_number: 123-456-7890 + sigv4: + access_key: '[REDACTED]' + profile: default + region: us-east-1 + role_arn: arn:aws:iam:us-east-1:0123456789:role/my-role + secret_key: '[REDACTED]' + subject: subject + target_arn: arn:aws:sns:us-east-1:0123456789:SNSTopicName + topic_arn: arn:aws:sns:us-east-1:0123456789:SNSTopicName + disableResolveMessage: true + - uid: teams-uid + type: teams + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + sectiontitle: test-second-title + title: test-title + url: http://localhost + disableResolveMessage: true + - uid: telegram-uid + type: telegram + settings: + bottoken: '[REDACTED]' + chatid: "12345678" + disable_notifications: true + disable_web_page_preview: true + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + message_thread_id: "13579" + parse_mode: html + protect_content: true + disableResolveMessage: true + - uid: threema-uid + type: threema + settings: + api_secret: '[REDACTED]' + description: test-description + gateway_id: '*1234567' + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + recipient_id: '*1234567' + title: test-title + disableResolveMessage: true + - uid: victorops-uid + type: victorops + settings: + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + messageType: test-messagetype + title: test-title + url: '[REDACTED]' + disableResolveMessage: true + - uid: webex-uid + type: webex + settings: + api_url: http://localhost + bot_token: '[REDACTED]' + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + room_id: test-room-id + disableResolveMessage: true + - uid: webhook-uid + type: webhook + settings: + authorization_scheme: basic + hmacConfig: + header: X-Grafana-Alerting-Signature + secret: '[REDACTED]' + timestampHeader: X-Grafana-Alerting-Timestamp + http_config: + oauth2: + client_id: test-client-id + client_secret: '[REDACTED]' + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: '[REDACTED]' + clientCertificate: '[REDACTED]' + clientKey: '[REDACTED]' + insecureSkipVerify: false + token_url: https://localhost/auth/token + httpMethod: PUT + maxAlerts: "2" + message: test-message + password: '[REDACTED]' + title: test-title + tlsConfig: + caCertificate: '[REDACTED]' + clientCertificate: '[REDACTED]' + clientKey: '[REDACTED]' + insecureSkipVerify: false + url: http://localhost + username: test-user + disableResolveMessage: true + - uid: wecom-uid + type: wecom + settings: + agent_id: test-agent_id + corp_id: test-corp_id + endpointUrl: http://localhost/test-endpointUrl + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + msgtype: markdown + secret: '[REDACTED]' + title: test-title + touser: test-touser + url: '[REDACTED]' + disableResolveMessage: true diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.hcl b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.hcl new file mode 100644 index 00000000000..a65f7bdf0d9 --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.hcl @@ -0,0 +1,304 @@ +resource "grafana_contact_point" "contact_point_2b661702215368fe" { + name = "all-integrations" + + alertmanager { + url = "https://alertmanager-01.com" + basic_auth_user = "grafana" + basic_auth_password = "admin" + } + + dingding { + url = "http://localhost" + message_type = "actionCard" + title = "Alerts firing: {{ len .Alerts.Firing }}" + message = "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved" + } + + discord { + url = "http://localhost" + title = "test-title" + message = "test-message" + avatar_url = "http://avatar" + use_discord_username = true + } + + email { + addresses = ["test@grafana.com"] + single_email = true + message = "test-message" + subject = "test-subject" + } + + googlechat { + url = "http://localhost" + title = "test-title" + message = "test-message" + } + + jira { + api_url = "http://localhost" + project = "Test Project" + issue_type = "Test Issue Type" + summary = "Test Summary" + description = "Test Description" + labels = ["Test Label", "Test Label 2"] + priority = "Test Priority" + reopen_transition = "Test Reopen Transition" + resolve_transition = "Test Resolve Transition" + wont_fix_resolution = "Test Won't Fix Resolution" + reopen_duration = "1m" + dedup_key_field = "10000" + fields = "{\"test-field\":\"test-value\"}" + user = "user" + password = "password" + } + + kafka { + rest_proxy_url = "http://localhost/" + topic = "test-topic" + description = "test-description" + details = "test-details" + username = "test-user" + password = "password" + api_version = "v2" + cluster_id = "12345" + } + + line { + token = "test" + title = "test-title" + description = "test-description" + } + + mqtt { + broker_url = "tcp://localhost:1883" + client_id = "grafana-test-client-id" + topic = "grafana/alerts" + message_format = "json" + username = "test-username" + password = "test-password" + qos = 0 + retain = false + + tls_config { + insecure_skip_verify = false + ca_certificate = "test-tls-ca-certificate" + client_certificate = "test-tls-client-certificate" + client_key = "test-tls-client-key" + } + } + + opsgenie { + api_key = "test-api-key" + url = "http://localhost" + message = "test-message" + description = "test-description" + auto_close = false + override_priority = false + send_tags_as = "both" + + responders { + id = "test-id" + type = "team" + } + responders { + username = "test-user" + type = "user" + } + responders { + name = "test-schedule" + type = "schedule" + } + } + + pagerduty { + integration_key = "test-api-key" + severity = "test-severity" + class = "test-class" + component = "test-component" + group = "test-group" + summary = "test-summary" + source = "test-source" + client = "test-client" + client_url = "http://localhost/test-client-url" + url = "http://localhost/test-api-url" + } + + oncall { + url = "http://localhost" + http_method = "PUT" + max_alerts = 2 + authorization_scheme = "basic" + basic_auth_user = "test-user" + basic_auth_password = "test-pass" + title = "test-title" + message = "test-message" + } + + pushover { + user_key = "test-user-key" + api_token = "test-api-token" + priority = 1 + ok_priority = 2 + retry = 555 + expire = 333 + device = "test-device" + sound = "test-sound" + ok_sound = "test-ok-sound" + title = "test-title" + message = "test-message" + upload_image = false + } + + sensugo { + url = "http://localhost" + api_key = "test-api-key" + entity = "test-entity" + check = "test-check" + namespace = "test-namespace" + handler = "test-handler" + message = "test-message" + } + + slack { + endpoint_url = "http://localhost/endpoint_url" + url = "http://localhost/url" + token = "test-token" + recipient = "test-recipient" + text = "test-text" + title = "test-title" + username = "test-username" + icon_emoji = "test-icon" + icon_url = "http://localhost/icon_url" + mention_channel = "channel" + mention_users = "test-mentionUsers" + mention_groups = "test-mentionGroups" + color = "test-color" + } + + sns { + api_url = "https://sns.us-east-1.amazonaws.com" + + sigv4 { + region = "us-east-1" + access_key = "access-key" + secret_key = "secret-key" + profile = "default" + role_arn = "arn:aws:iam:us-east-1:0123456789:role/my-role" + } + + topic_arn = "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + phone_number = "123-456-7890" + target_arn = "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + subject = "subject" + message = "message" + attributes = { + attr1 = "val1" + } + } + + teams { + url = "http://localhost" + message = "test-message" + title = "test-title" + section_title = "test-second-title" + } + + telegram { + token = "test-token" + chat_id = "12345678" + message_thread_id = "13579" + message = "test-message" + parse_mode = "html" + disable_web_page_preview = true + protect_content = true + disable_notifications = true + } + + threema { + gateway_id = "*1234567" + recipient_id = "*1234567" + api_secret = "test-secret" + title = "test-title" + description = "test-description" + } + + victorops { + url = "http://localhost" + message_type = "test-messagetype" + title = "test-title" + description = "test-description" + } + + webhook { + url = "http://localhost" + http_method = "PUT" + max_alerts = 2 + authorization_scheme = "basic" + basic_auth_user = "test-user" + basic_auth_password = "test-pass" + title = "test-title" + message = "test-message" + + tlsConfig { + insecure_skip_verify = false + ca_certificate = "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----" + client_certificate = "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----" + client_key = "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----" + } + + hmacConfig { + secret = "test-hmac-secret" + header = "X-Grafana-Alerting-Signature" + timestamp_header = "X-Grafana-Alerting-Timestamp" + } + + http_config { + + oauth2 { + client_id = "test-client-id" + client_secret = "test-client-secret" + token_url = "https://localhost/auth/token" + scopes = ["scope1", "scope2"] + endpoint_params = { + param1 = "value1" + param2 = "value2" + } + + tls_config { + insecure_skip_verify = false + ca_certificate = "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----" + client_certificate = "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----" + client_key = "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----" + } + + proxy_config { + proxy_url = "http://localproxy:8080" + no_proxy = "localhost" + proxy_from_environment = false + proxy_connect_header = { + X-Proxy-Header = "proxy-value" + } + } + } + } + } + + wecom { + url = "test-url" + secret = "test-secret" + agent_id = "test-agent_id" + corp_id = "test-corp_id" + message = "test-message" + title = "test-title" + msg_type = "markdown" + to_user = "test-touser" + } + + webex { + token = "12345" + api_url = "http://localhost" + message = "test-message" + room_id = "test-room-id" + } +} diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.json b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.json new file mode 100644 index 00000000000..1532bdd8ac4 --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.json @@ -0,0 +1,898 @@ +{ + "apiVersion": 1, + "contactPoints": [ + { + "orgId": 1, + "name": "all-integrations", + "receivers": [ + { + "uid": "dingding-uid", + "type": "dingding", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved", + "msgType": "actionCard", + "title": "Alerts firing: {{ len .Alerts.Firing }}", + "url": "http://localhost" + }, + "disableResolveMessage": false + }, + { + "uid": "discord-uid", + "type": "discord", + "settings": { + "avatar_url": "http://avatar", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "title": "test-title", + "url": "http://localhost", + "use_discord_username": true + }, + "disableResolveMessage": false + }, + { + "uid": "email-uid", + "type": "email", + "settings": { + "addresses": "test@grafana.com", + "message": "test-message", + "singleEmail": true, + "subject": "test-subject" + }, + "disableResolveMessage": false + }, + { + "uid": "googlechat-uid", + "type": "googlechat", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "title": "test-title", + "url": "http://localhost" + }, + "disableResolveMessage": false + }, + { + "uid": "jira-uid", + "type": "jira", + "settings": { + "api_url": "http://localhost", + "dedup_key_field": "10000", + "description": "Test Description", + "fields": { + "test-field": "test-value" + }, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "issue_type": "Test Issue Type", + "labels": [ + "Test Label", + "Test Label 2" + ], + "password": "password", + "priority": "Test Priority", + "project": "Test Project", + "reopen_duration": "1m", + "reopen_transition": "Test Reopen Transition", + "resolve_transition": "Test Resolve Transition", + "summary": "Test Summary", + "user": "user", + "wont_fix_resolution": "Test Won't Fix Resolution" + }, + "disableResolveMessage": false + }, + { + "uid": "kafka-uid", + "type": "kafka", + "settings": { + "apiVersion": "v2", + "description": "test-description", + "details": "test-details", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "kafkaClusterId": "12345", + "kafkaRestProxy": "http://localhost/", + "kafkaTopic": "test-topic", + "password": "password", + "username": "test-user" + }, + "disableResolveMessage": false + }, + { + "uid": "line-uid", + "type": "LINE", + "settings": { + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "title": "test-title", + "token": "test" + }, + "disableResolveMessage": false + }, + { + "uid": "mqtt-uid", + "type": "mqtt", + "settings": { + "brokerUrl": "tcp://localhost:1883", + "clientId": "grafana-test-client-id", + "messageFormat": "json", + "password": "test-password", + "qos": "0", + "retain": false, + "tlsConfig": { + "caCertificate": "test-tls-ca-certificate", + "clientCertificate": "test-tls-client-certificate", + "clientKey": "test-tls-client-key", + "insecureSkipVerify": false + }, + "topic": "grafana/alerts", + "username": "test-username" + }, + "disableResolveMessage": false + }, + { + "uid": "oncall-uid", + "type": "oncall", + "settings": { + "authorization_scheme": "basic", + "httpMethod": "PUT", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "maxAlerts": "2", + "message": "test-message", + "password": "test-pass", + "title": "test-title", + "url": "http://localhost", + "username": "test-user" + }, + "disableResolveMessage": false + }, + { + "uid": "opsgenie-uid", + "type": "opsgenie", + "settings": { + "apiKey": "test-api-key", + "apiUrl": "http://localhost", + "autoClose": false, + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "overridePriority": false, + "responders": [ + { + "id": "test-id", + "type": "team" + }, + { + "type": "user", + "username": "test-user" + }, + { + "name": "test-schedule", + "type": "schedule" + } + ], + "sendTagsAs": "both" + }, + "disableResolveMessage": false + }, + { + "uid": "pagerduty-uid", + "type": "pagerduty", + "settings": { + "class": "test-class", + "client": "test-client", + "client_url": "http://localhost/test-client-url", + "component": "test-component", + "group": "test-group", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "integrationKey": "test-api-key", + "severity": "test-severity", + "source": "test-source", + "summary": "test-summary", + "url": "http://localhost/test-api-url" + }, + "disableResolveMessage": false + }, + { + "uid": "prometheus-alertmanager-uid", + "type": "prometheus-alertmanager", + "settings": { + "basicAuthPassword": "admin", + "basicAuthUser": "grafana", + "url": "https://alertmanager-01.com" + }, + "disableResolveMessage": false + }, + { + "uid": "pushover-uid", + "type": "pushover", + "settings": { + "apiToken": "test-api-token", + "device": "test-device", + "expire": 333, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "okPriority": 2, + "okSound": "test-ok-sound", + "priority": 1, + "retry": 555, + "sound": "test-sound", + "title": "test-title", + "uploadImage": false, + "userKey": "test-user-key" + }, + "disableResolveMessage": false + }, + { + "uid": "sensugo-uid", + "type": "sensugo", + "settings": { + "apikey": "test-api-key", + "check": "test-check", + "entity": "test-entity", + "handler": "test-handler", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "namespace": "test-namespace", + "url": "http://localhost" + }, + "disableResolveMessage": false + }, + { + "uid": "slack-uid", + "type": "slack", + "settings": { + "color": "test-color", + "endpointUrl": "http://localhost/endpoint_url", + "icon_emoji": "test-icon", + "icon_url": "http://localhost/icon_url", + "mentionChannel": "channel", + "mentionGroups": "test-mentionGroups", + "mentionUsers": "test-mentionUsers", + "recipient": "test-recipient", + "text": "test-text", + "title": "test-title", + "token": "test-token", + "url": "http://localhost/url", + "username": "test-username" + }, + "disableResolveMessage": false + }, + { + "uid": "sns-uid", + "type": "sns", + "settings": { + "api_url": "https://sns.us-east-1.amazonaws.com", + "attributes": { + "attr1": "val1" + }, + "message": "message", + "phone_number": "123-456-7890", + "sigv4": { + "access_key": "access-key", + "profile": "default", + "region": "us-east-1", + "role_arn": "arn:aws:iam:us-east-1:0123456789:role/my-role", + "secret_key": "secret-key" + }, + "subject": "subject", + "target_arn": "arn:aws:sns:us-east-1:0123456789:SNSTopicName", + "topic_arn": "arn:aws:sns:us-east-1:0123456789:SNSTopicName" + }, + "disableResolveMessage": false + }, + { + "uid": "teams-uid", + "type": "teams", + "settings": { + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "sectiontitle": "test-second-title", + "title": "test-title", + "url": "http://localhost" + }, + "disableResolveMessage": false + }, + { + "uid": "telegram-uid", + "type": "telegram", + "settings": { + "bottoken": "test-token", + "chatid": "12345678", + "disable_notifications": true, + "disable_web_page_preview": true, + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "message_thread_id": "13579", + "parse_mode": "html", + "protect_content": true + }, + "disableResolveMessage": false + }, + { + "uid": "threema-uid", + "type": "threema", + "settings": { + "api_secret": "test-secret", + "description": "test-description", + "gateway_id": "*1234567", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "recipient_id": "*1234567", + "title": "test-title" + }, + "disableResolveMessage": false + }, + { + "uid": "victorops-uid", + "type": "victorops", + "settings": { + "description": "test-description", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "messageType": "test-messagetype", + "title": "test-title", + "url": "http://localhost" + }, + "disableResolveMessage": false + }, + { + "uid": "webex-uid", + "type": "webex", + "settings": { + "api_url": "http://localhost", + "bot_token": "12345", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "room_id": "test-room-id" + }, + "disableResolveMessage": false + }, + { + "uid": "webhook-uid", + "type": "webhook", + "settings": { + "authorization_scheme": "basic", + "hmacConfig": { + "header": "X-Grafana-Alerting-Signature", + "secret": "test-hmac-secret", + "timestampHeader": "X-Grafana-Alerting-Timestamp" + }, + "httpMethod": "PUT", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "maxAlerts": "2", + "message": "test-message", + "password": "test-pass", + "title": "test-title", + "tlsConfig": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "url": "http://localhost", + "username": "test-user" + }, + "disableResolveMessage": false + }, + { + "uid": "wecom-uid", + "type": "wecom", + "settings": { + "agent_id": "test-agent_id", + "corp_id": "test-corp_id", + "endpointUrl": "http://localhost/test-endpointUrl", + "http_config": { + "oauth2": { + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "endpoint_params": { + "param1": "value1", + "param2": "value2" + }, + "proxy_config": { + "no_proxy": "localhost", + "proxy_connect_header": { + "X-Proxy-Header": "proxy-value" + }, + "proxy_from_environment": false, + "proxy_url": "http://localproxy:8080" + }, + "scopes": [ + "scope1", + "scope2" + ], + "tls_config": { + "caCertificate": "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----", + "clientCertificate": "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----", + "clientKey": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----", + "insecureSkipVerify": false + }, + "token_url": "https://localhost/auth/token" + } + }, + "message": "test-message", + "msgtype": "markdown", + "secret": "test-secret", + "title": "test-title", + "touser": "test-touser", + "url": "test-url" + }, + "disableResolveMessage": false + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.yaml b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.yaml new file mode 100644 index 00000000000..a6af53fcb55 --- /dev/null +++ b/pkg/services/ngalert/api/test-data/receiver-exports/unredacted/all-integrations.yaml @@ -0,0 +1,1101 @@ +apiVersion: 1 +contactPoints: + - orgId: 1 + name: all-integrations + receivers: + - uid: dingding-uid + type: dingding + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: '{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved' + msgType: actionCard + title: 'Alerts firing: {{ len .Alerts.Firing }}' + url: http://localhost + disableResolveMessage: false + - uid: discord-uid + type: discord + settings: + avatar_url: http://avatar + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + title: test-title + url: http://localhost + use_discord_username: true + disableResolveMessage: false + - uid: email-uid + type: email + settings: + addresses: test@grafana.com + message: test-message + singleEmail: true + subject: test-subject + disableResolveMessage: false + - uid: googlechat-uid + type: googlechat + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + title: test-title + url: http://localhost + disableResolveMessage: false + - uid: jira-uid + type: jira + settings: + api_url: http://localhost + dedup_key_field: "10000" + description: Test Description + fields: + test-field: test-value + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + issue_type: Test Issue Type + labels: + - Test Label + - Test Label 2 + password: password + priority: Test Priority + project: Test Project + reopen_duration: 1m + reopen_transition: Test Reopen Transition + resolve_transition: Test Resolve Transition + summary: Test Summary + user: user + wont_fix_resolution: Test Won't Fix Resolution + disableResolveMessage: false + - uid: kafka-uid + type: kafka + settings: + apiVersion: v2 + description: test-description + details: test-details + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + kafkaClusterId: "12345" + kafkaRestProxy: http://localhost/ + kafkaTopic: test-topic + password: password + username: test-user + disableResolveMessage: false + - uid: line-uid + type: LINE + settings: + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + title: test-title + token: test + disableResolveMessage: false + - uid: mqtt-uid + type: mqtt + settings: + brokerUrl: tcp://localhost:1883 + clientId: grafana-test-client-id + messageFormat: json + password: test-password + qos: "0" + retain: false + tlsConfig: + caCertificate: test-tls-ca-certificate + clientCertificate: test-tls-client-certificate + clientKey: test-tls-client-key + insecureSkipVerify: false + topic: grafana/alerts + username: test-username + disableResolveMessage: false + - uid: oncall-uid + type: oncall + settings: + authorization_scheme: basic + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + httpMethod: PUT + maxAlerts: "2" + message: test-message + password: test-pass + title: test-title + url: http://localhost + username: test-user + disableResolveMessage: false + - uid: opsgenie-uid + type: opsgenie + settings: + apiKey: test-api-key + apiUrl: http://localhost + autoClose: false + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + overridePriority: false + responders: + - id: test-id + type: team + - type: user + username: test-user + - name: test-schedule + type: schedule + sendTagsAs: both + disableResolveMessage: false + - uid: pagerduty-uid + type: pagerduty + settings: + class: test-class + client: test-client + client_url: http://localhost/test-client-url + component: test-component + group: test-group + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + integrationKey: test-api-key + severity: test-severity + source: test-source + summary: test-summary + url: http://localhost/test-api-url + disableResolveMessage: false + - uid: prometheus-alertmanager-uid + type: prometheus-alertmanager + settings: + basicAuthPassword: admin + basicAuthUser: grafana + url: https://alertmanager-01.com + disableResolveMessage: false + - uid: pushover-uid + type: pushover + settings: + apiToken: test-api-token + device: test-device + expire: 333 + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + okPriority: 2 + okSound: test-ok-sound + priority: 1 + retry: 555 + sound: test-sound + title: test-title + uploadImage: false + userKey: test-user-key + disableResolveMessage: false + - uid: sensugo-uid + type: sensugo + settings: + apikey: test-api-key + check: test-check + entity: test-entity + handler: test-handler + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + namespace: test-namespace + url: http://localhost + disableResolveMessage: false + - uid: slack-uid + type: slack + settings: + color: test-color + endpointUrl: http://localhost/endpoint_url + icon_emoji: test-icon + icon_url: http://localhost/icon_url + mentionChannel: channel + mentionGroups: test-mentionGroups + mentionUsers: test-mentionUsers + recipient: test-recipient + text: test-text + title: test-title + token: test-token + url: http://localhost/url + username: test-username + disableResolveMessage: false + - uid: sns-uid + type: sns + settings: + api_url: https://sns.us-east-1.amazonaws.com + attributes: + attr1: val1 + message: message + phone_number: 123-456-7890 + sigv4: + access_key: access-key + profile: default + region: us-east-1 + role_arn: arn:aws:iam:us-east-1:0123456789:role/my-role + secret_key: secret-key + subject: subject + target_arn: arn:aws:sns:us-east-1:0123456789:SNSTopicName + topic_arn: arn:aws:sns:us-east-1:0123456789:SNSTopicName + disableResolveMessage: false + - uid: teams-uid + type: teams + settings: + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + sectiontitle: test-second-title + title: test-title + url: http://localhost + disableResolveMessage: false + - uid: telegram-uid + type: telegram + settings: + bottoken: test-token + chatid: "12345678" + disable_notifications: true + disable_web_page_preview: true + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + message_thread_id: "13579" + parse_mode: html + protect_content: true + disableResolveMessage: false + - uid: threema-uid + type: threema + settings: + api_secret: test-secret + description: test-description + gateway_id: '*1234567' + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + recipient_id: '*1234567' + title: test-title + disableResolveMessage: false + - uid: victorops-uid + type: victorops + settings: + description: test-description + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + messageType: test-messagetype + title: test-title + url: http://localhost + disableResolveMessage: false + - uid: webex-uid + type: webex + settings: + api_url: http://localhost + bot_token: "12345" + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + room_id: test-room-id + disableResolveMessage: false + - uid: webhook-uid + type: webhook + settings: + authorization_scheme: basic + hmacConfig: + header: X-Grafana-Alerting-Signature + secret: test-hmac-secret + timestampHeader: X-Grafana-Alerting-Timestamp + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + httpMethod: PUT + maxAlerts: "2" + message: test-message + password: test-pass + title: test-title + tlsConfig: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + url: http://localhost + username: test-user + disableResolveMessage: false + - uid: wecom-uid + type: wecom + settings: + agent_id: test-agent_id + corp_id: test-corp_id + endpointUrl: http://localhost/test-endpointUrl + http_config: + oauth2: + client_id: test-client-id + client_secret: test-client-secret + endpoint_params: + param1: value1 + param2: value2 + proxy_config: + no_proxy: localhost + proxy_connect_header: + X-Proxy-Header: proxy-value + proxy_from_environment: false + proxy_url: http://localproxy:8080 + scopes: + - scope1 + - scope2 + tls_config: + caCertificate: |- + -----BEGIN CERTIFICATE----- + MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx + MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60 + 2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3 + WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML + -----END CERTIFICATE----- + clientCertificate: |- + -----BEGIN CERTIFICATE----- + MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw + DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow + EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d + 7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B + 5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr + BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 + NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l + Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc + 6MF9+Yw1Yy0t + -----END CERTIFICATE----- + clientKey: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 + AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q + EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== + -----END EC PRIVATE KEY----- + insecureSkipVerify: false + token_url: https://localhost/auth/token + message: test-message + msgtype: markdown + secret: test-secret + title: test-title + touser: test-touser + url: test-url + disableResolveMessage: false diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index dfd39e73f56..7cf30d3b702 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -5,10 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "strings" "time" "github.com/go-openapi/strfmt" - alertingTemplates "github.com/grafana/alerting/templates" amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" @@ -17,6 +17,7 @@ import ( "github.com/grafana/alerting/definition" alertingmodels "github.com/grafana/alerting/models" + "github.com/grafana/grafana/pkg/apimachinery/errutil" ) @@ -267,9 +268,32 @@ type ( PostableApiReceiver = definition.PostableApiReceiver PostableGrafanaReceivers = definition.PostableGrafanaReceivers ReceiverType = definition.ReceiverType - MergeResult = definition.MergeResult ) +type MergeResult definition.MergeResult + +func (m MergeResult) LogContext() []any { + if len(m.RenamedReceivers) == 0 && len(m.RenamedTimeIntervals) == 0 { + return nil + } + logCtx := make([]any, 0, 4) + if len(m.RenamedTimeIntervals) > 0 { + rcvBuilder := strings.Builder{} + for from, to := range m.RenamedReceivers { + rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to)) + } + logCtx = append(logCtx, "renamedReceivers", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1])) + } + if len(m.RenamedTimeIntervals) > 0 { + rcvBuilder := strings.Builder{} + for from, to := range m.RenamedTimeIntervals { + rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to)) + } + logCtx = append(logCtx, "renamedTimeIntervals", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1])) + } + return logCtx +} + const ( GrafanaReceiverType = definition.GrafanaReceiverType AlertmanagerReceiverType = definition.AlertmanagerReceiverType @@ -721,12 +745,14 @@ func (c ExtraConfiguration) Validate() error { } } - // Alertmanager configuration is validated during YAML unmarshalling. - am := config.Config{} - err := yaml.Unmarshal([]byte(c.AlertmanagerConfig), &am) + cfg, err := c.GetAlertmanagerConfig() if err != nil { return errInvalidExtraConfiguration(fmt.Errorf("failed to parse alertmanager config: %w", err)) } + err = cfg.Validate() + if err != nil { + return errInvalidExtraConfiguration(fmt.Errorf("invalid alertmanager config: %w", err)) + } return nil } @@ -779,31 +805,20 @@ func (c *PostableUserConfig) GetMergedAlertmanagerConfig() (MergeResult, error) return MergeResult{}, fmt.Errorf("failed to get mimir alertmanager config: %w", err) } - return definition.Merge(c.AlertmanagerConfig, mcfg, opts) + m, err := definition.Merge(c.AlertmanagerConfig, mcfg, opts) + if err != nil { + return MergeResult{}, fmt.Errorf("failed to merge alertmanager config: %w", err) + } + return MergeResult(m), nil } -// GetMergedTemplateDefinitions converts the given PostableUserConfig's TemplateFiles to a slice of TemplateDefinitions. -func (c *PostableUserConfig) GetMergedTemplateDefinitions() []alertingTemplates.TemplateDefinition { - out := make([]alertingTemplates.TemplateDefinition, 0, len(c.TemplateFiles)) - for name, tmpl := range c.TemplateFiles { - out = append(out, alertingTemplates.TemplateDefinition{ - Name: name, - Template: tmpl, - Kind: alertingTemplates.GrafanaKind, - }) - } - if len(c.ExtraConfigs) == 0 { +// GetMergedTemplateDefinitions converts the given PostableUserConfig's TemplateFiles to a slice of Templates. +func (c *PostableUserConfig) GetMergedTemplateDefinitions() []definition.PostableApiTemplate { + out := definition.TemplatesMapToPostableAPITemplates(c.TemplateFiles, definition.GrafanaTemplateKind) + if len(c.ExtraConfigs) == 0 || len(c.ExtraConfigs[0].TemplateFiles) == 0 { return out } - // support only one config for now - for name, tmpl := range c.ExtraConfigs[0].TemplateFiles { - out = append(out, alertingTemplates.TemplateDefinition{ - Name: name, - Template: tmpl, - Kind: alertingTemplates.MimirKind, - }) - } - return out + return append(out, definition.TemplatesMapToPostableAPITemplates(c.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind)...) } func (c *PostableUserConfig) UnmarshalJSON(b []byte) error { diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go index 5f6fb5c2390..e6f074a7938 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - alertingTemplates "github.com/grafana/alerting/templates" + "github.com/grafana/alerting/definition" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/common/model" @@ -383,23 +383,113 @@ func TestPostableUserConfig_GetMergedTemplateDefinitions(t *testing.T) { require.Len(t, result, tc.expectedTemplates) templateMap := make(map[string]string) - kindMap := make(map[string]alertingTemplates.Kind) + kindMap := make(map[string]definition.TemplateKind) for _, tmpl := range result { - templateMap[tmpl.Name] = tmpl.Template + templateMap[tmpl.Name] = tmpl.Content kindMap[tmpl.Name] = tmpl.Kind } for name, content := range tc.config.TemplateFiles { require.Equal(t, content, templateMap[name]) - require.Equal(t, alertingTemplates.GrafanaKind, kindMap[name]) + require.Equal(t, definition.GrafanaTemplateKind, kindMap[name]) } if len(tc.config.ExtraConfigs) > 0 { for name, content := range tc.config.ExtraConfigs[0].TemplateFiles { require.Equal(t, content, templateMap[name]) - require.Equal(t, alertingTemplates.MimirKind, kindMap[name]) + require.Equal(t, definition.MimirTemplateKind, kindMap[name]) } } }) } } + +func TestExtraConfiguration_Validate(t *testing.T) { + testCases := []struct { + name string + config ExtraConfiguration + expectedError string + }{ + { + name: "valid configuration", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `route: + receiver: default +receivers: + - name: default`, + }, + }, + { + name: "empty identifier", + config: ExtraConfiguration{ + Identifier: "", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `route: {receiver: default}`, + }, + expectedError: "identifier is required", + }, + { + name: "invalid matcher type", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchNotEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `route: + receiver: default +receivers: + - name: default`, + }, + expectedError: "only matchers with type equal are supported", + }, + { + name: "invalid YAML alertmanager config", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `invalid: yaml: content: [`, + }, + expectedError: "failed to parse alertmanager config", + }, + { + name: "missing route in alertmanager config", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `receivers: + - name: default`, + }, + expectedError: "no routes provided", + }, + { + name: "missing receivers in alertmanager config", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: `route: + receiver: default`, + }, + expectedError: "undefined receiver", + }, + { + name: "empty alertmanager config", + config: ExtraConfiguration{ + Identifier: "test-config", + MergeMatchers: config.Matchers{{Type: labels.MatchEqual, Name: "env", Value: "prod"}}, + AlertmanagerConfig: "", + }, + expectedError: "failed to parse alertmanager config", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.config.Validate() + if tc.expectedError == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.expectedError) + } + }) + } +} diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 7f98ad65c92..e32d69f0282 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -69,16 +69,19 @@ type JiraIntegration struct { Project string `yaml:"project,omitempty" json:"project,omitempty" hcl:"project"` IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty" hcl:"issue_type"` - Summary *string `yaml:"summary,omitempty" json:"summary,omitempty" hcl:"summary"` - Description *string `yaml:"description,omitempty" json:"description,omitempty" hcl:"description"` - Labels *[]string `yaml:"labels,omitempty" json:"labels,omitempty" hcl:"labels"` - Priority *string `yaml:"priority,omitempty" json:"priority,omitempty" hcl:"priority"` - ReopenTransition *string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty" hcl:"reopen_transition"` - ResolveTransition *string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty" hcl:"resolve_transition"` - WontFixResolution *string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty" hcl:"wont_fix_resolution"` - ReopenDuration *string `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty" hcl:"reopen_duration"` - DedupKeyFieldName *string `yaml:"dedup_key_field,omitempty" json:"dedup_key_field,omitempty" hcl:"dedup_key_field"` - Fields *map[string]any `yaml:"fields,omitempty" json:"fields,omitempty" hcl:"fields"` + Summary *string `yaml:"summary,omitempty" json:"summary,omitempty" hcl:"summary"` + Description *string `yaml:"description,omitempty" json:"description,omitempty" hcl:"description"` + Labels *[]string `yaml:"labels,omitempty" json:"labels,omitempty" hcl:"labels"` + Priority *string `yaml:"priority,omitempty" json:"priority,omitempty" hcl:"priority"` + ReopenTransition *string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty" hcl:"reopen_transition"` + ResolveTransition *string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty" hcl:"resolve_transition"` + WontFixResolution *string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty" hcl:"wont_fix_resolution"` + ReopenDuration *string `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty" hcl:"reopen_duration"` + DedupKeyFieldName *string `yaml:"dedup_key_field,omitempty" json:"dedup_key_field,omitempty" hcl:"dedup_key_field"` + + // This should be a map[string]any but gohcl does not support encoding that type. Instead, we force it to a string + // using a jsoniter extension `mapToJSONStringCodec` which will be handled in the TF provider. + Fields *string `yaml:"fields,omitempty" json:"fields,omitempty" hcl:"fields"` User *Secret `yaml:"user,omitempty" json:"user,omitempty" hcl:"user"` Password *Secret `yaml:"password,omitempty" json:"password,omitempty" hcl:"password"` diff --git a/pkg/services/ngalert/eval/eval_test.go b/pkg/services/ngalert/eval/eval_test.go index 9f82f958b03..15a9d464b15 100644 --- a/pkg/services/ngalert/eval/eval_test.go +++ b/pkg/services/ngalert/eval/eval_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" fakes "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/user" @@ -591,7 +592,15 @@ func TestValidate(t *testing.T) { pluginsStore: store, }) - expressions := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest()) + expressions := expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + nil, + nil, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsclient.NewNullMTDatasourceClientBuilder(), + ) validator := NewConditionValidator(cacheService, expressions, store) evalCtx := NewContext(context.Background(), u) @@ -710,7 +719,19 @@ func TestCreate_HysteresisCommand(t *testing.T) { cache: cacheService, pluginsStore: store, }) - evaluator := NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest())) + evaluator := NewEvaluatorFactory( + setting.UnifiedAlertingSettings{}, + cacheService, + expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + nil, + nil, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsclient.NewNullMTDatasourceClientBuilder(), + ), + ) evalCtx := NewContextWithPreviousResults(context.Background(), u, testCase.reader) eval, err := evaluator.Create(evalCtx, condition) diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 0c2a0274867..c4f3d19ba24 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -193,7 +193,8 @@ func (ng *AlertNG) init() error { crypto := notifier.NewCrypto(ng.SecretsService, ng.store, moaLogger) remotePrimary := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemotePrimary) remoteSecondary := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemoteSecondary) - if remotePrimary || remoteSecondary { + remoteSecondaryWithRemoteState := ng.FeatureToggles.IsEnabled(initCtx, featuremgmt.FlagAlertmanagerRemoteSecondaryWithRemoteState) + if remotePrimary || remoteSecondary || remoteSecondaryWithRemoteState { m := ng.Metrics.GetRemoteAlertmanagerMetrics() smtpCfg := remoteClient.SmtpConfig{ FromAddress: ng.Cfg.Smtp.FromAddress, @@ -268,6 +269,10 @@ func (ng *AlertNG) init() error { cfg.OrgID = orgID remoteAM, err := createRemoteAlertmanager(ctx, cfg, ng.KVStore, crypto, autogenFn, m, ng.tracer) if err != nil { + if remoteSecondaryWithRemoteState { + // We can't start the internal Alertmanager without the remote state. + return nil, fmt.Errorf("failed to create remote Alertmanager, can't start the internal Alertmanager without the remote state: %w", err) + } moaLogger.Error("Failed to create remote Alertmanager, falling back to using only the internal one", "err", err) return internalAM, nil } diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index faf85e3f07a..8c833c14a2b 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "strconv" - "strings" "time" alertingNotify "github.com/grafana/alerting/notify" @@ -325,29 +324,6 @@ func (am *alertmanager) aggregateInhibitMatchers(rules []config.InhibitRule, amu } } -func logMergeResult(l log.Logger, m apimodels.MergeResult) { - if len(m.RenamedReceivers) == 0 && len(m.RenamedTimeIntervals) == 0 { - return - } - - logCtx := make([]any, 0, 4) - if len(m.RenamedTimeIntervals) > 0 { - rcvBuilder := strings.Builder{} - for from, to := range m.RenamedReceivers { - rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to)) - } - logCtx = append(logCtx, "renamedReceivers", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1])) - } - if len(m.RenamedTimeIntervals) > 0 { - rcvBuilder := strings.Builder{} - for from, to := range m.RenamedTimeIntervals { - rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to)) - } - logCtx = append(logCtx, "renamedTimeIntervals", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1])) - } - l.Info("Configurations merged successfully but some resources were renamed", logCtx...) -} - // applyConfig applies a new configuration by re-initializing all components using the configuration provided. // It returns a boolean indicating whether the user config was changed and an error. // It is not safe to call concurrently. @@ -361,9 +337,11 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable if err != nil { return false, fmt.Errorf("failed to get full alertmanager configuration: %w", err) } - logMergeResult(am.logger, mergeResult) + if logInfo := mergeResult.LogContext(); len(logInfo) > 0 { + am.logger.Info("Configurations merged successfully but some resources were renamed", logInfo...) + } amConfig := mergeResult.Config - templates := cfg.GetMergedTemplateDefinitions() + templates := alertingNotify.PostableAPITemplatesToTemplateDefinitions(cfg.GetMergedTemplateDefinitions()) // Now add autogenerated config to the route. err = AddAutogenConfig(ctx, am.logger, am.Store, am.Base.TenantID(), &amConfig, skipInvalid) diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 16b43e1a92d..befa524f468 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -143,6 +143,22 @@ func TestPrometheusRulesToGrafana(t *testing.T) { }, expectError: false, }, + { + name: "rule group with empty name", + orgID: 1, + namespace: "namespaceUID", + promGroup: PrometheusRuleGroup{ + Name: "", + Rules: []PrometheusRule{ + { + Alert: "alert-1", + Expr: "up == 0", + }, + }, + }, + expectError: true, + errorMsg: "rule group name must not be empty", + }, { name: "recording rule", orgID: 1, diff --git a/pkg/services/ngalert/prom/models.go b/pkg/services/ngalert/prom/models.go index 1ac5ad1d0d9..1664a31c1b6 100644 --- a/pkg/services/ngalert/prom/models.go +++ b/pkg/services/ngalert/prom/models.go @@ -7,10 +7,14 @@ import ( ) var ( - ErrPrometheusRuleValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleInvalid") - ErrPrometheusRuleGroupValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleGroupInvalid") + errPrometheusRuleGroupValidationFailedMsg = "{{.Public.Message}}" + ErrPrometheusRuleGroupValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleGroupInvalid").MustTemplate(errPrometheusRuleGroupValidationFailedMsg, errutil.WithPublic(errPrometheusRuleGroupValidationFailedMsg)) ) +func errPrometheusRuleGroupValidationFailed(message string) error { + return ErrPrometheusRuleGroupValidationFailed.Build(errutil.TemplateData{Public: map[string]any{"Message": message}}) +} + type PrometheusRulesFile struct { Groups []PrometheusRuleGroup `yaml:"groups"` } @@ -25,12 +29,16 @@ type PrometheusRuleGroup struct { } func (g *PrometheusRuleGroup) Validate() error { + if g.Name == "" { + return errPrometheusRuleGroupValidationFailed("rule group name must not be empty") + } + if g.Limit != 0 { - return ErrPrometheusRuleGroupValidationFailed.Errorf("limit is not supported") + return errPrometheusRuleGroupValidationFailed("limit is not supported") } if g.QueryOffset != nil && *g.QueryOffset < prommodel.Duration(0) { - return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset must be >= 0") + return errPrometheusRuleGroupValidationFailed("query_offset must be >= 0") } return nil diff --git a/pkg/services/ngalert/provisioning/mute_timings.go b/pkg/services/ngalert/provisioning/mute_timings.go index 8ed7d93a597..6ad0f7efd65 100644 --- a/pkg/services/ngalert/provisioning/mute_timings.go +++ b/pkg/services/ngalert/provisioning/mute_timings.go @@ -440,6 +440,12 @@ func replaceMuteTiming(route *definitions.Route, oldName, newName string) int { updated++ } } + for idx := range route.ActiveTimeIntervals { + if route.ActiveTimeIntervals[idx] == oldName { + route.ActiveTimeIntervals[idx] = newName + updated++ + } + } for _, route := range route.Routes { updated += replaceMuteTiming(route, oldName, newName) } diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index d80f6967692..e577f7334e1 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -12,16 +12,16 @@ import ( "time" "github.com/go-openapi/strfmt" + alertingClusterPB "github.com/grafana/alerting/cluster/clusterpb" + "github.com/grafana/alerting/definition" + alertingModels "github.com/grafana/alerting/models" + alertingNotify "github.com/grafana/alerting/notify" amalert "github.com/prometheus/alertmanager/api/v2/client/alert" amalertgroup "github.com/prometheus/alertmanager/api/v2/client/alertgroup" amgeneral "github.com/prometheus/alertmanager/api/v2/client/general" amsilence "github.com/prometheus/alertmanager/api/v2/client/silence" "github.com/prometheus/client_golang/prometheus" - alertingClusterPB "github.com/grafana/alerting/cluster/clusterpb" - alertingModels "github.com/grafana/alerting/models" - alertingNotify "github.com/grafana/alerting/notify" - "gopkg.in/yaml.v3" "github.com/grafana/grafana/pkg/infra/log" @@ -185,30 +185,12 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto return nil, err } - // Parse the default configuration into a postable config. - pCfg, err := notifier.Load([]byte(cfg.DefaultConfig)) - if err != nil { - return nil, err - } - - if err := autogenFn(ctx, logger, cfg.OrgID, &pCfg.AlertmanagerConfig, true); err != nil { - return nil, err - } - - rawCfg, err := json.Marshal(pCfg) - if err != nil { - return nil, err - } - - // Initialize LastReadinessCheck so it's present even if the check fails. - metrics.LastReadinessCheck.Set(0) - - return &Alertmanager{ + am := &Alertmanager{ amClient: amc, autogenFn: autogenFn, crypto: crypto, - defaultConfig: string(rawCfg), - defaultConfigHash: fmt.Sprintf("%x", md5.Sum(rawCfg)), + defaultConfig: cfg.DefaultConfig, + defaultConfigHash: "", // calculated below log: logger, metrics: metrics, mimirClient: mc, @@ -222,7 +204,31 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto // TODO: Remove once it can be sent only in the 'smtp_config' field. smtpFrom: cfg.SmtpFrom, - }, nil + } + + // Parse the default configuration once and remember its hash so we can compare it later. + // Known edge case: assigning a default contact point to a rule and setting route overrides + // (grouping, group timing, time intervals etc) changes the autogenerated configuration. + // The `default` flag is sent to the remote Alertmanager for informational purposes, so we can tolerate this. + err = func() error { + defaultCfg, err := am.buildConfiguration(ctx, []byte(cfg.DefaultConfig)) + if err != nil { + return fmt.Errorf("unable to build default configuration: %w", err) + } + rawDefaultCfg, err := json.Marshal(defaultCfg) + if err != nil { + return fmt.Errorf("unable to marshal default configuration: %w", err) + } + am.defaultConfigHash = fmt.Sprintf("%x", md5.Sum(rawDefaultCfg)) + return nil + }() + if err != nil { + logger.Error("Unable to calculate hash of the default configuration. Remote Alertmanager will always get isDefault=false", "error", err) + } + // Initialize LastReadinessCheck so it's present even if the check fails. + metrics.LastReadinessCheck.Set(0) + + return am, nil } // ApplyConfig is called by the multi-org Alertmanager on startup and on every sync loop iteration (1m default). @@ -274,67 +280,26 @@ func (am *Alertmanager) checkReadiness(ctx context.Context) error { // CompareAndSendConfiguration checks whether a given configuration is being used by the remote Alertmanager. // If not, it sends the configuration to the remote Alertmanager. func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config *models.AlertConfiguration) error { - c, err := notifier.Load([]byte(config.AlertmanagerConfiguration)) + payload, err := am.buildConfiguration(ctx, []byte(config.AlertmanagerConfiguration)) if err != nil { - return err + return fmt.Errorf("unable to build configuration: %w", err) } - - // Add auto-generated routes and decrypt before comparing. - if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil { - return err - } - - decryptedCfg, err := am.decryptConfiguration(ctx, c) - if err != nil { - return err - } - - // Decrypt and merge extra configs - if err := am.mergeExtraConfigs(ctx, decryptedCfg); err != nil { - return fmt.Errorf("unable to merge extra configurations: %w", err) - } - payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg) rawPayload, err := json.Marshal(payload) if err != nil { return fmt.Errorf("unable to marshal decrypted configuration: %w", err) } - - configHash := md5.Sum(rawPayload) + configHash := fmt.Sprintf("%x", md5.Sum(rawPayload)) // Send the configuration only if we need to. if !am.shouldSendConfig(ctx, configHash) { return nil } - return am.sendConfiguration(ctx, payload, fmt.Sprintf("%x", configHash), config.CreatedAt, am.isDefaultConfiguration(configHash)) + return am.sendConfiguration(ctx, payload, configHash, config.CreatedAt, am.isDefaultConfiguration(configHash)) } -func (am *Alertmanager) isDefaultConfiguration(configHash [16]byte) bool { - return fmt.Sprintf("%x", configHash) == am.defaultConfigHash -} - -// decryptConfiguration creates a copy of the configuration, decrypts it, and returns the decrypted configuration alongside its hash. -// Should not be used outside of this package and the specific use case of decrypting the configuration before sending -// it to the remote Alertmanager. -func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) (*apimodels.PostableUserConfig, error) { - // Create a copy of the configuration to avoid modifying the original - cfgCopy := &apimodels.PostableUserConfig{} - rawCfg, err := json.Marshal(cfg) - if err != nil { - return nil, fmt.Errorf("unable to marshal original configuration: %w", err) - } - if err := json.Unmarshal(rawCfg, cfgCopy); err != nil { - return nil, fmt.Errorf("unable to unmarshal original configuration: %w", err) - } - - // Decrypt the receivers in the configuration. - decryptedReceivers, err := legacy_storage.DecryptedReceivers(cfgCopy.AlertmanagerConfig.Receivers, decrypter(ctx, am.crypto)) - if err != nil { - return nil, fmt.Errorf("unable to decrypt receivers: %w", err) - } - cfgCopy.AlertmanagerConfig.Receivers = decryptedReceivers - - return cfgCopy, nil +func (am *Alertmanager) isDefaultConfiguration(configHash string) bool { + return configHash == am.defaultConfigHash } func decrypter(ctx context.Context, crypto Crypto) models.DecryptFn { @@ -351,28 +316,48 @@ func decrypter(ctx context.Context, crypto Crypto) models.DecryptFn { } } -// mergeExtraConfigs decrypts and applies merged configuration if extra configs exist. -func (am *Alertmanager) mergeExtraConfigs(ctx context.Context, config *apimodels.PostableUserConfig) error { - if len(config.ExtraConfigs) == 0 { - return nil - } - - if err := am.crypto.DecryptExtraConfigs(ctx, config); err != nil { - return fmt.Errorf("unable to decrypt extra configs: %w", err) - } - - mergeResult, err := config.GetMergedAlertmanagerConfig() +// buildConfiguration takes a raw Alertmanager configuration and returns a config that the remote Alertmanager can use. +// It parses the initial configuration, adds auto-generated routes, decrypts receivers, and merges the extra configs. +func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte) (remoteClient.GrafanaAlertmanagerConfig, error) { + c, err := notifier.Load(raw) if err != nil { - return fmt.Errorf("unable to get merged Alertmanager configuration: %w", err) + return remoteClient.GrafanaAlertmanagerConfig{}, err } - config.AlertmanagerConfig = mergeResult.Config - // Clear ExtraConfigs to avoid re-processing them later - config.ExtraConfigs = nil - return nil + // Add auto-generated routes and decrypt before comparing. + if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil { + return remoteClient.GrafanaAlertmanagerConfig{}, err + } + + // Decrypt the receivers in the configuration. + decryptedReceivers, err := legacy_storage.DecryptedReceivers(c.AlertmanagerConfig.Receivers, decrypter(ctx, am.crypto)) + if err != nil { + return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to decrypt receivers: %w", err) + } + c.AlertmanagerConfig.Receivers = decryptedReceivers + + if err := am.crypto.DecryptExtraConfigs(ctx, c); err != nil { + return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to decrypt extra configs: %w", err) + } + + mergeResult, err := c.GetMergedAlertmanagerConfig() + if err != nil { + return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to get merged Alertmanager configuration: %w", err) + } + + var templates []definition.PostableApiTemplate + if len(c.ExtraConfigs) > 0 && len(c.ExtraConfigs[0].TemplateFiles) > 0 { + templates = definition.TemplatesMapToPostableAPITemplates(c.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind) + } + + return remoteClient.GrafanaAlertmanagerConfig{ + TemplateFiles: c.TemplateFiles, + AlertmanagerConfig: mergeResult.Config, + Templates: templates, + }, nil } -func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg *remoteClient.GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error { +func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg remoteClient.GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error { am.metrics.ConfigSyncsTotal.Inc() if err := am.mimirClient.CreateGrafanaAlertmanagerConfig( ctx, @@ -446,19 +431,17 @@ func (am *Alertmanager) SendState(ctx context.Context) error { // SaveAndApplyConfig decrypts and sends a configuration to the remote Alertmanager. func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.PostableUserConfig) error { - // Add auto-generated routes and decrypt before sending. - if err := am.autogenFn(ctx, am.log, am.orgID, &cfg.AlertmanagerConfig, false); err != nil { - return err - } - decryptedCfg, err := am.decryptConfiguration(ctx, cfg) + // Copy the configuration by marshalling to avoid any mutations to the provided configuration. + rawCopy, err := json.Marshal(cfg) if err != nil { return err } - if err := am.mergeExtraConfigs(ctx, decryptedCfg); err != nil { - return fmt.Errorf("unable to merge extra configurations: %w", err) + payload, err := am.buildConfiguration(ctx, rawCopy) + if err != nil { + return fmt.Errorf("unable to build configuration: %w", err) } - payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg) + rawCfg, err := json.Marshal(payload) if err != nil { return err @@ -470,21 +453,12 @@ func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.P // SaveAndApplyDefaultConfig sends the default Grafana Alertmanager configuration to the remote Alertmanager. func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error { - c, err := notifier.Load([]byte(am.defaultConfig)) + am.log.Debug("Sending default configuration to a remote Alertmanager", "url", am.url) + payload, err := am.buildConfiguration(ctx, []byte(am.defaultConfig)) if err != nil { - return fmt.Errorf("unable to parse the default configuration: %w", err) + return fmt.Errorf("unable to build default configuration: %w", err) } - // Add auto-generated routes and decrypt before sending. - if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil { - return err - } - decryptedCfg, err := am.decryptConfiguration(ctx, c) - if err != nil { - return err - } - - payload := PostableUserConfigToGrafanaAlertmanagerConfig(decryptedCfg) rawCfg, err := json.Marshal(payload) if err != nil { return err @@ -736,7 +710,7 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { // shouldSendConfig compares the remote Alertmanager configuration with our local one. // It returns true if the configurations are different. -func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash [16]byte) bool { +func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash string) bool { rc, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) if err != nil { // Log the error and return true so we try to upload our config anyway. @@ -775,11 +749,5 @@ func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash [16]byte) boo } } - // Hash and compare Alertmanager configs. - rawRemote, err := json.Marshal(rc.GrafanaAlertmanagerConfig) - if err != nil { - am.log.Error("Unable to marshal the remote Alertmanager configuration for comparison", "err", err) - return true - } - return md5.Sum(rawRemote) != hash + return rc.Hash != hash } diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index 5aad1a47a1a..bf1d9f0bfbd 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -411,10 +411,9 @@ func TestIntegrationApplyConfig(t *testing.T) { require.Equal(t, 4, configSyncs) require.Equal(t, am.smtp, configSent.SmtpConfig) - // Failing to add the auto-generated routes should result in an error. + // Failing to add the auto-generated routes should not result in an error. _, err = NewAlertmanager(context.Background(), cfg, fstore, notifier.NewCrypto(secretsService, nil, log.NewNopLogger()), errAutogenFn, m, tracing.InitializeTracerForTest()) - require.ErrorIs(t, err, errTest) - require.Equal(t, 4, configSyncs) + require.NoError(t, err, errTest) } func TestCompareAndSendConfiguration(t *testing.T) { @@ -484,12 +483,18 @@ func TestCompareAndSendConfiguration(t *testing.T) { test, err := notifier.Load([]byte(testGrafanaConfigWithSecret)) require.NoError(t, err) - cfgWithDecryptedSecret := PostableUserConfigToGrafanaAlertmanagerConfig(test) + cfgWithDecryptedSecret := client.GrafanaAlertmanagerConfig{ + TemplateFiles: test.TemplateFiles, + AlertmanagerConfig: test.AlertmanagerConfig, + } testAutogenRoutes, err := notifier.Load([]byte(testGrafanaConfigWithSecret)) require.NoError(t, err) require.NoError(t, testAutogenFn(nil, nil, 0, &testAutogenRoutes.AlertmanagerConfig, false)) - cfgWithAutogenRoutes := PostableUserConfigToGrafanaAlertmanagerConfig(testAutogenRoutes) + cfgWithAutogenRoutes := client.GrafanaAlertmanagerConfig{ + TemplateFiles: testAutogenRoutes.TemplateFiles, + AlertmanagerConfig: testAutogenRoutes.AlertmanagerConfig, + } // Calculate hashes for expected configurations cfgWithDecryptedSecretBytes, err := json.Marshal(cfgWithDecryptedSecret) @@ -506,9 +511,10 @@ func TestCompareAndSendConfiguration(t *testing.T) { require.NoError(t, err) r, err := cfgWithExtraUnmerged.GetMergedAlertmanagerConfig() require.NoError(t, err) - cfgWithExtraMerged := &client.GrafanaAlertmanagerConfig{ + cfgWithExtraMerged := client.GrafanaAlertmanagerConfig{ TemplateFiles: cfgWithExtraUnmerged.TemplateFiles, AlertmanagerConfig: r.Config, + Templates: definition.TemplatesMapToPostableAPITemplates(cfgWithExtraUnmerged.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind), } cfgWithExtraMergedBytes, err := json.Marshal(cfgWithExtraMerged) require.NoError(t, err) @@ -720,7 +726,7 @@ func Test_isDefaultConfiguration(t *testing.T) { } raw, err := json.Marshal(test.config) require.NoError(tt, err) - require.Equal(tt, test.expected, am.isDefaultConfiguration(md5.Sum(raw))) + require.Equal(tt, test.expected, am.isDefaultConfiguration(fmt.Sprintf("%x", md5.Sum(raw)))) }) } } @@ -827,7 +833,7 @@ func TestCompareAndSendConfigurationWithExtraConfigs(t *testing.T) { // Return an empty config to ensure it gets replaced w.Header().Add("content-type", "application/json") require.NoError(t, json.NewEncoder(w).Encode(client.UserGrafanaConfig{ - GrafanaAlertmanagerConfig: &client.GrafanaAlertmanagerConfig{}, + GrafanaAlertmanagerConfig: client.GrafanaAlertmanagerConfig{}, })) return } diff --git a/pkg/services/ngalert/remote/client/alertmanager_configuration.go b/pkg/services/ngalert/remote/client/alertmanager_configuration.go index 35239700ea7..998f785ffd6 100644 --- a/pkg/services/ngalert/remote/client/alertmanager_configuration.go +++ b/pkg/services/ngalert/remote/client/alertmanager_configuration.go @@ -17,24 +17,26 @@ const ( ) type GrafanaAlertmanagerConfig struct { + // TODO this needs to be deleted once Mimir is updated TemplateFiles map[string]string `yaml:"template_files" json:"template_files"` AlertmanagerConfig definition.PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"` + Templates []definition.PostableApiTemplate `yaml:"templates,omitempty" json:"templates,omitempty"` } func (u *GrafanaAlertmanagerConfig) MarshalJSON() ([]byte, error) { - // this is special marshaling that makes sure that secrets are not masked + // This is special marshaling that makes sure that secrets are not masked. type cfg GrafanaAlertmanagerConfig return definition.MarshalJSONWithSecrets((*cfg)(u)) } type UserGrafanaConfig struct { - GrafanaAlertmanagerConfig *GrafanaAlertmanagerConfig `json:"configuration"` - Hash string `json:"configuration_hash"` - CreatedAt int64 `json:"created"` - Default bool `json:"default"` - Promoted bool `json:"promoted"` - ExternalURL string `json:"external_url"` - SmtpConfig SmtpConfig `json:"smtp_config"` + GrafanaAlertmanagerConfig GrafanaAlertmanagerConfig `json:"configuration"` + Hash string `json:"configuration_hash"` + CreatedAt int64 `json:"created"` + Default bool `json:"default"` + Promoted bool `json:"promoted"` + ExternalURL string `json:"external_url"` + SmtpConfig SmtpConfig `json:"smtp_config"` // TODO: Remove once everything can be sent in the 'SmtpConfig' field. SmtpFrom string `json:"smtp_from"` @@ -64,7 +66,7 @@ func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafana return gc, nil } -func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg *GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error { +func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error { payload, err := definition.MarshalJSONWithSecrets(&UserGrafanaConfig{ GrafanaAlertmanagerConfig: cfg, Hash: hash, diff --git a/pkg/services/ngalert/remote/client/mimir.go b/pkg/services/ngalert/remote/client/mimir.go index 3bf10465076..59c3a6ad1ee 100644 --- a/pkg/services/ngalert/remote/client/mimir.go +++ b/pkg/services/ngalert/remote/client/mimir.go @@ -30,7 +30,7 @@ type MimirClient interface { DeleteGrafanaAlertmanagerState(ctx context.Context) error GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) - CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration *GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error + CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error DeleteGrafanaAlertmanagerConfig(ctx context.Context) error TestTemplate(ctx context.Context, c alertingNotify.TestTemplatesConfigBodyParams) (*alertingNotify.TestTemplatesResults, error) diff --git a/pkg/services/ngalert/remote/compat.go b/pkg/services/ngalert/remote/compat.go deleted file mode 100644 index 8914bc96728..00000000000 --- a/pkg/services/ngalert/remote/compat.go +++ /dev/null @@ -1,13 +0,0 @@ -package remote - -import ( - "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - "github.com/grafana/grafana/pkg/services/ngalert/remote/client" -) - -func PostableUserConfigToGrafanaAlertmanagerConfig(config *definitions.PostableUserConfig) *client.GrafanaAlertmanagerConfig { - return &client.GrafanaAlertmanagerConfig{ - TemplateFiles: config.TemplateFiles, - AlertmanagerConfig: config.AlertmanagerConfig, - } -} diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 565de4320f4..af8769a2e88 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -84,6 +84,75 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { } }) + t.Run("ApplyConfig - with remote state", func(tt *testing.T) { + { + // During the first ApplyConfig call, we should: + // 1. Apply the configuration to the remote Alertmanager + // 2. Merge the remote state + // 3. Apply the configuration to the internal Alertmanager + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary, withRemoteState) + readyCall := remote.EXPECT().Ready().Return(false).Once() + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(readyCall) + remote.EXPECT().Ready().Return(true).Once() + remoteStateCall := remote.EXPECT().GetRemoteState(mock.Anything).Return(notifier.ExternalState{}, nil).Once() + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(remoteStateCall) + require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) + require.True(tt, internal.mergeStateCalled) + + // We shouldn't attempt to merge the remote state again on the next sync loop iteration. + internal.mergeStateCalled = false + remote.EXPECT().Ready().Return(true).Once() + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() + remote.EXPECT().CompareAndSendConfiguration(ctx, mock.Anything).Return(nil).Once() + require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) + require.False(tt, internal.mergeStateCalled) + } + + { + // If we fail to apply the configuration in the remote Alertmanager, we should get an error and not start the internal Alertmanager. + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary, withSyncInterval(10*time.Minute), withRemoteState) + readyCall := remote.EXPECT().Ready().Return(false).Once() + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once().NotBefore(readyCall) + remote.EXPECT().Ready().Return(false).Once() + err := forked.ApplyConfig(ctx, &models.AlertConfiguration{}) + require.Equal(tt, "remote Alertmanager not ready, can't fetch remote state", err.Error()) + require.False(tt, internal.mergeStateCalled) + + // Calling ApplyConfig again should result in the forked Alertmanager calling ApplyConfig on both + // Alertmanagers and merging the remote state, even if the sync interval has not elapsed. + remote.EXPECT().Ready().Return(true).Twice() + remote.EXPECT().CompareAndSendConfiguration(ctx, mock.Anything).Return(nil).Once() + remoteStateCall := remote.EXPECT().GetRemoteState(mock.Anything).Return(notifier.ExternalState{}, nil).Once() + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(remoteStateCall) + require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) + require.True(tt, internal.mergeStateCalled) + } + + { + // An error in the remote Alertmanager should be returned. + // The internal Alertmanager shouldn't be started. + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() + require.ErrorIs(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{}), expErr) + require.False(t, internal.mergeStateCalled) + } + + { + // An error in the internal Alertmanager should be returned. + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary, withRemoteState) + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() + + // Simulate starting the remote Alertmanager and merging the remote state. + readyCall := remote.EXPECT().Ready().Return(false).Once() + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(readyCall) + remote.EXPECT().Ready().Return(true).Once() + remote.EXPECT().GetRemoteState(mock.Anything).Return(notifier.ExternalState{}, nil).Once() + + require.ErrorIs(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{}), expErr) + require.True(t, internal.mergeStateCalled) + } + }) + t.Run("SaveAndApplyConfig", func(tt *testing.T) { // SaveAndApplyConfig should only be called on the remote Alertmanager. // State and configuration are updated on an interval. @@ -707,6 +776,11 @@ func (m *internalAlertmanagerMock) MergeState(notifier.ExternalState) error { return nil } +func withRemoteState(rsc RemoteSecondaryConfig) RemoteSecondaryConfig { + rsc.WithRemoteState = true + return rsc +} + func withSyncInterval(syncInterval time.Duration) func(RemoteSecondaryConfig) RemoteSecondaryConfig { return func(rsc RemoteSecondaryConfig) RemoteSecondaryConfig { rsc.SyncInterval = syncInterval diff --git a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go index 33a77c10f1c..1199b65ae9c 100644 --- a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go +++ b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go @@ -13,6 +13,8 @@ import ( models "github.com/grafana/grafana/pkg/services/ngalert/models" + notifier "github.com/grafana/grafana/pkg/services/ngalert/notifier" + notify "github.com/grafana/alerting/notify" v2models "github.com/prometheus/alertmanager/api/v2/models" @@ -413,6 +415,62 @@ func (_c *RemoteAlertmanagerMock_GetReceivers_Call) RunAndReturn(run func(contex return _c } +// GetRemoteState provides a mock function with given fields: _a0 +func (_m *RemoteAlertmanagerMock) GetRemoteState(_a0 context.Context) (notifier.ExternalState, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetRemoteState") + } + + var r0 notifier.ExternalState + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (notifier.ExternalState, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) notifier.ExternalState); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(notifier.ExternalState) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoteAlertmanagerMock_GetRemoteState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRemoteState' +type RemoteAlertmanagerMock_GetRemoteState_Call struct { + *mock.Call +} + +// GetRemoteState is a helper method to define mock.On call +// - _a0 context.Context +func (_e *RemoteAlertmanagerMock_Expecter) GetRemoteState(_a0 interface{}) *RemoteAlertmanagerMock_GetRemoteState_Call { + return &RemoteAlertmanagerMock_GetRemoteState_Call{Call: _e.mock.On("GetRemoteState", _a0)} +} + +func (_c *RemoteAlertmanagerMock_GetRemoteState_Call) Run(run func(_a0 context.Context)) *RemoteAlertmanagerMock_GetRemoteState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *RemoteAlertmanagerMock_GetRemoteState_Call) Return(_a0 notifier.ExternalState, _a1 error) *RemoteAlertmanagerMock_GetRemoteState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RemoteAlertmanagerMock_GetRemoteState_Call) RunAndReturn(run func(context.Context) (notifier.ExternalState, error)) *RemoteAlertmanagerMock_GetRemoteState_Call { + _c.Call.Return(run) + return _c +} + // GetSilence provides a mock function with given fields: _a0, _a1 func (_m *RemoteAlertmanagerMock) GetSilence(_a0 context.Context, _a1 string) (v2models.GettableSilence, error) { ret := _m.Called(_a0, _a1) diff --git a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go index fa018672ee7..45a24ee1dca 100644 --- a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go @@ -22,6 +22,7 @@ type configStore interface { type remoteAlertmanager interface { notifier.Alertmanager CompareAndSendConfiguration(context.Context, *models.AlertConfiguration) error + GetRemoteState(context.Context) (notifier.ExternalState, error) SendState(context.Context) error } @@ -35,6 +36,8 @@ type RemoteSecondaryForkedAlertmanager struct { lastSync time.Time syncInterval time.Duration + + shouldFetchRemoteState bool } type RemoteSecondaryConfig struct { @@ -45,6 +48,9 @@ type RemoteSecondaryConfig struct { // SyncInterval determines how often we should attempt to synchronize // the configuration on the remote Alertmanager. SyncInterval time.Duration + + // WithRemoteState is used to fetch and merge the state from the remote Alertmanager before starting the internal one. + WithRemoteState bool } func (c *RemoteSecondaryConfig) Validate() error { @@ -59,12 +65,13 @@ func NewRemoteSecondaryForkedAlertmanager(cfg RemoteSecondaryConfig, internal no return nil, err } return &RemoteSecondaryForkedAlertmanager{ - log: cfg.Logger, - orgID: cfg.OrgID, - store: cfg.Store, - internal: internal, - remote: remote, - syncInterval: cfg.SyncInterval, + log: cfg.Logger, + orgID: cfg.OrgID, + store: cfg.Store, + internal: internal, + remote: remote, + syncInterval: cfg.SyncInterval, + shouldFetchRemoteState: cfg.WithRemoteState, }, nil } @@ -99,6 +106,28 @@ func (fam *RemoteSecondaryForkedAlertmanager) ApplyConfig(ctx context.Context, c } }() + if fam.shouldFetchRemoteState { + wg.Wait() + if !fam.remote.Ready() { + return fmt.Errorf("remote Alertmanager not ready, can't fetch remote state") + } + // Pull and merge the remote Alertmanager state. + rs, err := fam.remote.GetRemoteState(ctx) + if err != nil { + return fmt.Errorf("failed to fetch remote state: %w", err) + } + + // The internal Alertmanager should implement the StateMerger interface. + sm := fam.internal.(notifier.StateMerger) + if err := sm.MergeState(rs); err != nil { + return fmt.Errorf("failed to merge remote state: %w", err) + } + fam.log.Info("Successfully merged remote silences and nflog entries") + + // This operation should only be performed at startup. + fam.shouldFetchRemoteState = false + } + // Call ApplyConfig on the internal Alertmanager - we only care about errors for this one. err := fam.internal.ApplyConfig(ctx, config) wg.Wait() diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index df249d9e8cd..981a643c005 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" datasources "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" @@ -66,7 +67,19 @@ func TestProcessTicks(t *testing.T) { } cacheServ := &datasources.FakeCacheService{} - evaluator := eval.NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheServ, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest())) + evaluator := eval.NewEvaluatorFactory( + setting.UnifiedAlertingSettings{}, + cacheServ, + expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + nil, + nil, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsclient.NewNullMTDatasourceClientBuilder(), + ), + ) rrSet := setting.RecordingRuleSettings{ Enabled: true, } @@ -1192,7 +1205,19 @@ func setupScheduler(t *testing.T, rs *fakeRulesStore, is *state.FakeInstanceStor var evaluator = evalMock if evalMock == nil { - evaluator = eval.NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, &datasources.FakeCacheService{}, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest())) + evaluator = eval.NewEvaluatorFactory( + setting.UnifiedAlertingSettings{}, + &datasources.FakeCacheService{}, + expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + nil, + nil, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsclient.NewNullMTDatasourceClientBuilder(), + ), + ) } if registry == nil { diff --git a/pkg/services/ngalert/store/provisioning_store.go b/pkg/services/ngalert/store/provisioning_store.go index d6999eec31d..f1306168134 100644 --- a/pkg/services/ngalert/store/provisioning_store.go +++ b/pkg/services/ngalert/store/provisioning_store.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ngalert/models" ) @@ -70,25 +71,61 @@ func (st DBstore) SetProvenance(ctx context.Context, o models.Provisionable, org // TODO: Add a unit-of-work pattern, so updating objects + provenance will happen consistently with rollbacks across stores. // TODO: Need to make sure that writing a record where our concurrency key fails will also fail the whole transaction. That way, this gets rolled back too. can't just check that 0 updates happened inmemory. Check with jp. If not possible, we need our own concurrency key. // TODO: Clean up stale provenance records periodically. - upsertSQL := st.SQLStore.GetDialect().UpsertSQL( - provenanceRecord{}.TableName(), - []string{"record_key", "record_type", "org_id"}, - []string{"record_key", "record_type", "org_id", "provenance"}) - params := []interface{}{ - recordKey, - recordType, - org, - p, + if st.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingProvenanceLockWrites) { + return st.setProvenanceWithLocking(sess, recordKey, recordType, org, p) } + return st.setProvenanceUpsert(sess, recordKey, recordType, org, p) + }) +} - _, err := sess.SQL(upsertSQL, params...).Query() +func (st DBstore) setProvenanceUpsert(sess *db.Session, recordKey, recordType string, org int64, p models.Provenance) error { + upsertSQL := st.SQLStore.GetDialect().UpsertSQL( + provenanceRecord{}.TableName(), + []string{"record_key", "record_type", "org_id"}, + []string{"record_key", "record_type", "org_id", "provenance"}) + + params := []interface{}{ + recordKey, + recordType, + org, + p, + } + + _, err := sess.SQL(upsertSQL, params...).Query() + if err != nil { + return fmt.Errorf("failed to store provisioning status: %w", err) + } + return nil +} + +func (st DBstore) setProvenanceWithLocking(sess *db.Session, recordKey, recordType string, org int64, p models.Provenance) error { + // Check if the record exists with FOR UPDATE lock. + // If it does, we just update, otherwise we upsert the record. + // This is done to avoid deadlocks that can occur in MySQL when multiple transactions try to + // insert records (even different) because of the gap and insert intention locks. + exists, err := sess.Table(provenanceRecord{}). + Where("record_key = ? AND record_type = ? AND org_id = ?", recordKey, recordType, org). + ForUpdate(). + Exist() + if err != nil { + return fmt.Errorf("failed to check if provenance record exists: %w", err) + } + + if exists { + _, err = sess.Table(provenanceRecord{}). + Where("record_key = ? AND record_type = ? AND org_id = ?", recordKey, recordType, org). + Update(map[string]interface{}{ + "provenance": p, + }) if err != nil { return fmt.Errorf("failed to store provisioning status: %w", err) } - return nil - }) + } + + // Still upsert in case it was created while we were checking + return st.setProvenanceUpsert(sess, recordKey, recordType, org, p) } // DeleteProvenance deletes the provenance record from the table diff --git a/pkg/services/ngalert/store/provisioning_store_test.go b/pkg/services/ngalert/store/provisioning_store_test.go index 568f870027d..ea4daf9811b 100644 --- a/pkg/services/ngalert/store/provisioning_store_test.go +++ b/pkg/services/ngalert/store/provisioning_store_test.go @@ -2,10 +2,15 @@ package store_test import ( "context" + "fmt" + "math/rand" + "sync" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" @@ -19,113 +24,206 @@ func TestIntegrationProvisioningStore(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - store := createProvisioningStoreSut(tests.SetupTestEnv(t, testAlertingIntervalSeconds)) - t.Run("Default provenance of a known type is None", func(t *testing.T) { - rule := models.AlertRule{ - UID: "asdf", + testCases := []struct { + name string + featureEnabled bool + }{ + { + name: "without feature flag", + featureEnabled: false, + }, + { + name: "with feature flag", + featureEnabled: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ng, dbStore := tests.SetupTestEnv(t, testAlertingIntervalSeconds) + if tc.featureEnabled { + dbStore.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertingProvenanceLockWrites) + } + store := createProvisioningStoreSut(ng, dbStore) + + t.Run("Default provenance of a known type is None", func(t *testing.T) { + rule := models.AlertRule{ + UID: "asdf", + } + + provenance, err := store.GetProvenance(context.Background(), &rule, 1) + + require.NoError(t, err) + require.Equal(t, models.ProvenanceNone, provenance) + }) + + t.Run("Store returns saved provenance type", func(t *testing.T) { + rule := models.AlertRule{ + UID: "123", + } + err := store.SetProvenance(context.Background(), &rule, 1, models.ProvenanceFile) + require.NoError(t, err) + + p, err := store.GetProvenance(context.Background(), &rule, 1) + + require.NoError(t, err) + require.Equal(t, models.ProvenanceFile, p) + }) + + t.Run("Store does not get provenance of record with different org ID", func(t *testing.T) { + ruleOrg2 := models.AlertRule{ + UID: "456", + } + ruleOrg3 := models.AlertRule{ + UID: "456", + } + err := store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceFile) + require.NoError(t, err) + + p, err := store.GetProvenance(context.Background(), &ruleOrg3, 3) + + require.NoError(t, err) + require.Equal(t, models.ProvenanceNone, p) + }) + + t.Run("Store only updates provenance of record with given org ID", func(t *testing.T) { + ruleOrg2 := models.AlertRule{ + UID: "789", + OrgID: 2, + } + ruleOrg3 := models.AlertRule{ + UID: "789", + OrgID: 3, + } + err := store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceFile) + require.NoError(t, err) + err = store.SetProvenance(context.Background(), &ruleOrg3, 3, models.ProvenanceFile) + require.NoError(t, err) + + err = store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceAPI) + require.NoError(t, err) + + p, err := store.GetProvenance(context.Background(), &ruleOrg2, 2) + require.NoError(t, err) + require.Equal(t, models.ProvenanceAPI, p) + p, err = store.GetProvenance(context.Background(), &ruleOrg3, 3) + require.NoError(t, err) + require.Equal(t, models.ProvenanceFile, p) + }) + + t.Run("Store should return all provenances by type", func(t *testing.T) { + const orgID = 123 + rule1 := models.AlertRule{ + UID: "789", + OrgID: orgID, + } + rule2 := models.AlertRule{ + UID: "790", + OrgID: orgID, + } + err := store.SetProvenance(context.Background(), &rule1, orgID, models.ProvenanceFile) + require.NoError(t, err) + err = store.SetProvenance(context.Background(), &rule2, orgID, models.ProvenanceAPI) + require.NoError(t, err) + + p, err := store.GetProvenances(context.Background(), orgID, rule1.ResourceType()) + require.NoError(t, err) + require.Len(t, p, 2) + require.Equal(t, models.ProvenanceFile, p[rule1.UID]) + require.Equal(t, models.ProvenanceAPI, p[rule2.UID]) + }) + + t.Run("Store should delete provenance correctly", func(t *testing.T) { + const orgID = 1234 + ruleOrg := models.AlertRule{ + UID: "7834539", + OrgID: orgID, + } + err := store.SetProvenance(context.Background(), &ruleOrg, orgID, models.ProvenanceFile) + require.NoError(t, err) + p, err := store.GetProvenance(context.Background(), &ruleOrg, orgID) + require.NoError(t, err) + require.Equal(t, models.ProvenanceFile, p) + + err = store.DeleteProvenance(context.Background(), &ruleOrg, orgID) + require.NoError(t, err) + + p, err = store.GetProvenance(context.Background(), &ruleOrg, orgID) + require.NoError(t, err) + require.Equal(t, models.ProvenanceNone, p) + }) + }) + } +} + +func TestSetProvenance_DeadlockScenarios(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ng, dbStore := tests.SetupTestEnv(t, testAlertingIntervalSeconds) + dbStore.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertingProvenanceLockWrites) + store := createProvisioningStoreSut(ng, dbStore) + concurrency := 20 + + t.Run("Same record, different orgs", func(t *testing.T) { + rule := &models.AlertRule{UID: "same-record-diff-orgs"} + + var wg sync.WaitGroup + for i := range concurrency { + wg.Add(1) + go func(orgID int64) { + defer wg.Done() + time.Sleep(time.Microsecond * time.Duration(rand.Intn(100))) + err := store.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI) + require.NoError(t, err) + }(int64(i + 1)) } - - provenance, err := store.GetProvenance(context.Background(), &rule, 1) - - require.NoError(t, err) - require.Equal(t, models.ProvenanceNone, provenance) + wg.Wait() }) - t.Run("Store returns saved provenance type", func(t *testing.T) { - rule := models.AlertRule{ - UID: "123", + t.Run("Different records, same org", func(t *testing.T) { + orgID := int64(1) + + var wg sync.WaitGroup + for i := range concurrency { + wg.Add(1) + go func(uid string) { + defer wg.Done() + time.Sleep(time.Microsecond * time.Duration(rand.Intn(100))) + rule := &models.AlertRule{UID: uid} + err := store.SetProvenance(context.Background(), rule, orgID, models.ProvenanceFile) + require.NoError(t, err) + }(fmt.Sprintf("diff-record-same-org-%d", i)) } - err := store.SetProvenance(context.Background(), &rule, 1, models.ProvenanceFile) - require.NoError(t, err) - - p, err := store.GetProvenance(context.Background(), &rule, 1) - - require.NoError(t, err) - require.Equal(t, models.ProvenanceFile, p) + wg.Wait() }) - t.Run("Store does not get provenance of record with different org ID", func(t *testing.T) { - ruleOrg2 := models.AlertRule{ - UID: "456", + t.Run("Mixed operations", func(t *testing.T) { + rule := &models.AlertRule{UID: "mixed-ops"} + orgID := int64(1) + + var wg sync.WaitGroup + // Mix SetProvenance and GetProvenance operations + for range concurrency { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Microsecond * time.Duration(rand.Intn(100))) + err := store.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI) + require.NoError(t, err) + }() + + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Microsecond * time.Duration(rand.Intn(100))) + _, err := store.GetProvenance(context.Background(), rule, orgID) + require.NoError(t, err) + }() } - ruleOrg3 := models.AlertRule{ - UID: "456", - } - err := store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceFile) - require.NoError(t, err) - - p, err := store.GetProvenance(context.Background(), &ruleOrg3, 3) - - require.NoError(t, err) - require.Equal(t, models.ProvenanceNone, p) - }) - - t.Run("Store only updates provenance of record with given org ID", func(t *testing.T) { - ruleOrg2 := models.AlertRule{ - UID: "789", - OrgID: 2, - } - ruleOrg3 := models.AlertRule{ - UID: "789", - OrgID: 3, - } - err := store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceFile) - require.NoError(t, err) - err = store.SetProvenance(context.Background(), &ruleOrg3, 3, models.ProvenanceFile) - require.NoError(t, err) - - err = store.SetProvenance(context.Background(), &ruleOrg2, 2, models.ProvenanceAPI) - require.NoError(t, err) - - p, err := store.GetProvenance(context.Background(), &ruleOrg2, 2) - require.NoError(t, err) - require.Equal(t, models.ProvenanceAPI, p) - p, err = store.GetProvenance(context.Background(), &ruleOrg3, 3) - require.NoError(t, err) - require.Equal(t, models.ProvenanceFile, p) - }) - - t.Run("Store should return all provenances by type", func(t *testing.T) { - const orgID = 123 - rule1 := models.AlertRule{ - UID: "789", - OrgID: orgID, - } - rule2 := models.AlertRule{ - UID: "790", - OrgID: orgID, - } - err := store.SetProvenance(context.Background(), &rule1, orgID, models.ProvenanceFile) - require.NoError(t, err) - err = store.SetProvenance(context.Background(), &rule2, orgID, models.ProvenanceAPI) - require.NoError(t, err) - - p, err := store.GetProvenances(context.Background(), orgID, rule1.ResourceType()) - require.NoError(t, err) - require.Len(t, p, 2) - require.Equal(t, models.ProvenanceFile, p[rule1.UID]) - require.Equal(t, models.ProvenanceAPI, p[rule2.UID]) - }) - - t.Run("Store should delete provenance correctly", func(t *testing.T) { - const orgID = 1234 - ruleOrg := models.AlertRule{ - UID: "7834539", - OrgID: orgID, - } - err := store.SetProvenance(context.Background(), &ruleOrg, orgID, models.ProvenanceFile) - require.NoError(t, err) - p, err := store.GetProvenance(context.Background(), &ruleOrg, orgID) - require.NoError(t, err) - require.Equal(t, models.ProvenanceFile, p) - - err = store.DeleteProvenance(context.Background(), &ruleOrg, orgID) - require.NoError(t, err) - - p, err = store.GetProvenance(context.Background(), &ruleOrg, orgID) - require.NoError(t, err) - require.Equal(t, models.ProvenanceNone, p) + wg.Wait() }) } diff --git a/pkg/services/pluginsintegration/pipeline/steps.go b/pkg/services/pluginsintegration/pipeline/steps.go index 3e33fda53be..8ac3b8a5aa2 100644 --- a/pkg/services/pluginsintegration/pipeline/steps.go +++ b/pkg/services/pluginsintegration/pipeline/steps.go @@ -223,10 +223,6 @@ func NewAsExternalStep(cfg *config.PluginManagementCfg) *AsExternal { // Filter will filter out any plugins that are marked to be disabled. func (c *AsExternal) Filter(cl plugins.Class, bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) { - if !c.cfg.Features.ExternalCorePluginsEnabled { - return bundles, nil - } - if cl == plugins.ClassCore { res := []*plugins.FoundBundle{} for _, bundle := range bundles { diff --git a/pkg/services/pluginsintegration/pipeline/steps_test.go b/pkg/services/pluginsintegration/pipeline/steps_test.go index 24b7def7bde..009d489d2ba 100644 --- a/pkg/services/pluginsintegration/pipeline/steps_test.go +++ b/pkg/services/pluginsintegration/pipeline/steps_test.go @@ -68,9 +68,6 @@ func TestAsExternal(t *testing.T) { t.Run("should skip a core plugin", func(t *testing.T) { cfg := &config.PluginManagementCfg{ - Features: config.Features{ - ExternalCorePluginsEnabled: true, - }, PluginSettings: setting.PluginSettings{ "plugin1": map[string]string{ "as_external": "true", diff --git a/pkg/services/pluginsintegration/pluginconfig/config.go b/pkg/services/pluginsintegration/pluginconfig/config.go index 86408ae0e6f..ba57d743cfe 100644 --- a/pkg/services/pluginsintegration/pluginconfig/config.go +++ b/pkg/services/pluginsintegration/pluginconfig/config.go @@ -30,13 +30,11 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro cfg.PluginsCDNURLTemplate, cfg.AppURL, config.Features{ - ExternalCorePluginsEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalCorePlugins), - SkipHostEnvVarsEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSkipHostEnvVars), - SriChecksEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSriChecks), - PluginsCDNSyncLoaderEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsCDNSyncLoader), - LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins), - TempoAlertingEnabled: features.IsEnabledGlobally(featuremgmt.FlagTempoAlerting), - PluginAssetProvider: features.IsEnabledGlobally(featuremgmt.FlagPluginAssetProvider), + SkipHostEnvVarsEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSkipHostEnvVars), + SriChecksEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSriChecks), + LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins), + TempoAlertingEnabled: features.IsEnabledGlobally(featuremgmt.FlagTempoAlerting), + PluginAssetProvider: features.IsEnabledGlobally(featuremgmt.FlagPluginAssetProvider), }, cfg.GrafanaComAPIURL, cfg.DisablePlugins, diff --git a/pkg/services/publicdashboards/api/common_test.go b/pkg/services/publicdashboards/api/common_test.go index b31cb7c4831..d022408d82f 100644 --- a/pkg/services/publicdashboards/api/common_test.go +++ b/pkg/services/publicdashboards/api/common_test.go @@ -26,6 +26,7 @@ import ( datasourceService "github.com/grafana/grafana/pkg/services/datasources/service" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/licensing/licensingtest" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" @@ -155,6 +156,7 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP &fakeDataSourceRequestValidator{}, fpc, pCtxProvider, + mtdsclient.NewNullMTDatasourceClientBuilder(), ) } diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index 905e7e2cb4f..c3b508d5627 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -196,7 +196,7 @@ func TestAPIQueryPublicDashboard(t *testing.T) { } }` - setup := func(enabled bool) (*web.Mux, *publicdashboards.FakePublicDashboardService) { + setup := func(_ bool) (*web.Mux, *publicdashboards.FakePublicDashboardService) { service := publicdashboards.NewFakePublicDashboardService(t) testServer := setupTestServer(t, nil, service, anonymousUser) diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index 43c6505da1d..d3f05438e45 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -2,6 +2,7 @@ package query import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "golang.org/x/sync/errgroup" + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -22,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/setting" @@ -47,6 +50,7 @@ func ProvideService( dataSourceRequestValidator validations.DataSourceRequestValidator, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider, + mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder, ) *ServiceImpl { g := &ServiceImpl{ cfg: cfg, @@ -57,6 +61,7 @@ func ProvideService( pCtxProvider: pCtxProvider, log: log.New("query_data"), concurrentQueryLimit: cfg.SectionWithEnvOverrides("query").Key("concurrent_query_limit").MustInt(runtime.NumCPU()), + mtDatasourceClientBuilder: mtDatasourceClientBuilder, } g.log.Info("Query Service initialization") return g @@ -80,6 +85,8 @@ type ServiceImpl struct { pCtxProvider *plugincontext.Provider log log.Logger concurrentQueryLimit int + mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder + headers map[string]string } // Run ServiceImpl. @@ -201,7 +208,19 @@ func buildErrorResponses(err error, queries []*simplejson.Json) splitResponse { return splitResponse{er, http.Header{}} } -// handleExpressions handles POST /api/ds/query when there is an expression. +func QueryData(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder, headers map[string]string) (*backend.QueryDataResponse, error) { + s := &ServiceImpl{ + log: log, + dataSourceCache: dscache, + expressionService: exprService, + dataSourceRequestValidator: validations.ProvideValidator(), + mtDatasourceClientBuilder: mtDatasourceClientBuilder, + headers: headers, + } + return s.QueryData(ctx, nil, false, reqDTO) +} + +// handleExpressions handles queries when there is an expression. func (s *ServiceImpl) handleExpressions(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { exprReq := expr.Request{ Queries: []expr.Query{}, @@ -257,21 +276,42 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden } } - pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, ds.Type, user, ds) - if err != nil { - return nil, err - } req := &backend.QueryDataRequest{ - PluginContext: pCtx, - Headers: map[string]string{}, - Queries: []backend.DataQuery{}, + Headers: map[string]string{}, + Queries: []backend.DataQuery{}, } for _, q := range queries { req.Queries = append(req.Queries, q.query) } - return s.pluginClient.QueryData(ctx, req) + mtDsClient, ok := s.mtDatasourceClientBuilder.BuildClient(ds.Type, ds.UID) + if !ok { // single tenant flow + pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, ds.Type, user, ds) + if err != nil { + return nil, err + } + req.PluginContext = pCtx + return s.pluginClient.QueryData(ctx, req) + } else { // multi tenant flow + // transform request from backend.QueryDataRequest to k8s request + k8sReq := &data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: req.Queries[0].TimeRange.From.Format(time.RFC3339), + To: req.Queries[0].TimeRange.To.Format(time.RFC3339), + }, + } + for _, q := range req.Queries { + var dataQuery data.DataQuery + err := json.Unmarshal(q.JSON, &dataQuery) + if err != nil { + return nil, err + } + + k8sReq.Queries = append(k8sReq.Queries, dataQuery) + } + return mtDsClient.QueryData(ctx, *k8sReq) + } } // parseRequest parses a request into parsed queries grouped by datasource uid diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index e98331326f6..e4d430aa5b3 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,12 +25,14 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" @@ -49,7 +52,7 @@ func TestMain(m *testing.M) { func TestIntegrationParseMetricRequest(t *testing.T) { t.Run("Test a simple single datasource query", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -73,7 +76,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a single datasource query with expressions", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -113,7 +116,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a simple mixed datasource query", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -146,7 +149,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a mixed datasource query with expressions", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -207,7 +210,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Header validation", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -249,7 +252,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }) t.Run("Test a duplicated refId", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", "datasource": { @@ -270,7 +273,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { func TestIntegrationQueryDataMultipleSources(t *testing.T) { t.Run("can query multiple datasources", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -319,7 +322,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("can query multiple datasources with an expression present", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -385,7 +388,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("error is returned in query when one of the queries fails", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, _ := simplejson.NewJson([]byte(` { @@ -425,7 +428,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) t.Run("ignores a deprecated datasourceID", func(t *testing.T) { - tc := setup(t) + tc := setup(t, false, nil) query1, err := simplejson.NewJson([]byte(` { "datasource": { @@ -451,7 +454,63 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) } -func setup(t *testing.T) *testContext { +func TestIntegrationQueryDataWithMTDSClient(t *testing.T) { + t.Run("can run a simple datasource query with a mt ds client", func(t *testing.T) { + stubbedResponse := &backend.QueryDataResponse{Responses: make(backend.Responses)} + testClient := &testClient{ + queryDataStubbedResponse: stubbedResponse, + } + tc := setup(t, true, testClient) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`) + mr.From = "2022-01-01" + mr.To = "2022-01-02" + ctx := context.Background() + _, err := tc.queryService.QueryData(ctx, tc.signedInUser, true, mr) + require.NoError(t, err) + + assert.Equal(t, data.QueryDataRequest{ + TimeRange: data.TimeRange{ + From: "2022-01-01T00:00:00Z", + To: "2022-01-02T00:00:00Z", + }, + Queries: []data.DataQuery{ + { + CommonQueryProperties: data.CommonQueryProperties{ + RefID: "A", + Datasource: &data.DataSourceRef{ + Type: "postgres", + UID: "gIEkMvIVz", + }, + }, + }, + { + CommonQueryProperties: data.CommonQueryProperties{ + RefID: "B", + Datasource: &data.DataSourceRef{ + Type: "postgres", + UID: "gIEkMvIVz", + }, + }, + }, + }, + Debug: false, + }, testClient.queryDataLastCalledWith) + }) +} + +func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClient) *testContext { dss := []*datasources.DataSource{ {UID: "gIEkMvIVz", Type: "postgres"}, {UID: "sEx6ZvSVk", Type: "testdata"}, @@ -467,24 +526,55 @@ func setup(t *testing.T) *testContext { sqlStore, cfg := db.InitTestDBWithCfg(t) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) ss := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) + fakeDatasourceService := &fakeDatasources.FakeDataSourceService{ DataSources: dss, SimulatePluginFailure: false, } - pCtxProvider := plugincontext.ProvideService(cfg, - localcache.ProvideService(), &pluginstore.FakePluginStore{ + pCtxProvider := plugincontext.ProvideService( + cfg, + localcache.ProvideService(), + &pluginstore.FakePluginStore{ PluginList: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "postgres"}}, {JSONData: plugins.JSONData{ID: "testdata"}}, {JSONData: plugins.JSONData{ID: "mysql"}}, }, - }, &fakeDatasources.FakeCacheService{}, fakeDatasourceService, - pluginSettings.ProvideService(sqlStore, secretsService), pluginconfig.NewFakePluginRequestConfigProvider(), + }, + &fakeDatasources.FakeCacheService{}, + fakeDatasourceService, + pluginSettings.ProvideService(sqlStore, secretsService), + pluginconfig.NewFakePluginRequestConfigProvider(), ) - exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, pCtxProvider, - featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest()) - queryService := ProvideService(setting.NewCfg(), dc, exprService, rv, pc, pCtxProvider) // provider belonging to this package + + var mtdsClientBuilder mtdsclient.MTDatasourceClientBuilder + if isMultiTenant { + mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(isMultiTenant, mockClient) + } else { + mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(false, nil) + } + + exprService := expr.ProvideService( + &setting.Cfg{ExpressionsEnabled: true}, + pc, + pCtxProvider, + featuremgmt.WithFeatures(), + nil, + tracing.InitializeTracerForTest(), + mtdsClientBuilder, + ) + + queryService := ProvideService( + setting.NewCfg(), + dc, + exprService, + rv, + pc, + pCtxProvider, + mtdsClientBuilder, + ) + return &testContext{ pluginContext: pc, secretStore: ss, @@ -572,3 +662,20 @@ func (c *fakePluginClient) QueryData(ctx context.Context, req *backend.QueryData return &backend.QueryDataResponse{Responses: make(backend.Responses)}, nil } + +type testClient struct { + queryDataLastCalledWith data.QueryDataRequest + queryDataStubbedResponse *backend.QueryDataResponse + queryDataStubbedError error +} + +func (c *testClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) { + c.queryDataLastCalledWith = req + if c.queryDataStubbedError != nil { + return nil, c.queryDataStubbedError + } + if c.queryDataStubbedResponse != nil { + return c.queryDataStubbedResponse, nil + } + return nil, errors.New("no response stubbed") +} diff --git a/pkg/services/scimutil/scim_util.go b/pkg/services/scimutil/scim_util.go index 92d0ac74270..30998d4f5d9 100644 --- a/pkg/services/scimutil/scim_util.go +++ b/pkg/services/scimutil/scim_util.go @@ -44,23 +44,23 @@ func (s *SCIMUtil) IsUserSyncEnabled(ctx context.Context, orgID int64, staticEna return staticEnabled } -// AreNonProvisionedUsersAllowed checks if non-provisioned users are allowed using dynamic configuration with static fallback -func (s *SCIMUtil) AreNonProvisionedUsersAllowed(ctx context.Context, orgID int64, staticAllowed bool) bool { +// AreNonProvisionedUsersRejected checks if non-provisioned users are rejected using dynamic configuration with static fallback +func (s *SCIMUtil) AreNonProvisionedUsersRejected(ctx context.Context, orgID int64, staticRejected bool) bool { if s.k8sClient == nil { s.logger.Debug("K8s client not configured, using static SCIM config for non-provisioned users") - return staticAllowed + return staticRejected } - dynamicAllowed, dynamicConfigFetched := s.fetchDynamicSCIMSetting(ctx, orgID, "allowNonProvisionedUsers") + dynamicRejected, dynamicConfigFetched := s.fetchDynamicSCIMSetting(ctx, orgID, "rejectNonProvisionedUsers") if dynamicConfigFetched { - s.logger.Debug("Using dynamic SCIM config for user sync", "orgID", orgID, "enabled", dynamicAllowed) - return dynamicAllowed + s.logger.Debug("Using dynamic SCIM config for user sync", "orgID", orgID, "enabled", dynamicRejected) + return dynamicRejected } // Fallback to static config if dynamic config wasn't fetched successfully - s.logger.Debug("Using static SCIM config for user sync", "orgID", orgID, "enabled", staticAllowed) - return staticAllowed + s.logger.Debug("Using static SCIM config for user sync", "orgID", orgID, "enabled", staticRejected) + return staticRejected } // fetchDynamicSCIMSetting attempts to retrieve a specific dynamic SCIM configuration setting @@ -82,8 +82,8 @@ func (s *SCIMUtil) fetchDynamicSCIMSetting(ctx context.Context, orgID int64, set enabled = scimConfig.EnableUserSync case "group": enabled = scimConfig.EnableGroupSync - case "allowNonProvisionedUsers": - enabled = scimConfig.AllowNonProvisionedUsers + case "rejectNonProvisionedUsers": + enabled = scimConfig.RejectNonProvisionedUsers default: s.logger.Error("Invalid setting type provided to fetchDynamicSCIMSetting", "settingType", settingType) return false, false @@ -108,9 +108,9 @@ func (s *SCIMUtil) getOrgSCIMConfig(ctx context.Context, orgID int64) (*SCIMConf // SCIMConfigSpec represents the spec part of a SCIMConfig resource type SCIMConfigSpec struct { - EnableUserSync bool `json:"enableUserSync"` - EnableGroupSync bool `json:"enableGroupSync"` - AllowNonProvisionedUsers bool `json:"allowNonProvisionedUsers"` + EnableUserSync bool `json:"enableUserSync"` + EnableGroupSync bool `json:"enableGroupSync"` + RejectNonProvisionedUsers bool `json:"rejectNonProvisionedUsers"` } // unstructuredToSCIMConfig converts an unstructured object to a SCIMConfigSpec @@ -130,11 +130,11 @@ func (s *SCIMUtil) unstructuredToSCIMConfig(obj *unstructured.Unstructured) (*SC enableUserSync, _, _ := unstructured.NestedBool(spec, "enableUserSync") enableGroupSync, _, _ := unstructured.NestedBool(spec, "enableGroupSync") - allowNonProvisionedUsers, _, _ := unstructured.NestedBool(spec, "allowNonProvisionedUsers") + rejectNonProvisionedUsers, _, _ := unstructured.NestedBool(spec, "rejectNonProvisionedUsers") return &SCIMConfigSpec{ - EnableUserSync: enableUserSync, - EnableGroupSync: enableGroupSync, - AllowNonProvisionedUsers: allowNonProvisionedUsers, + EnableUserSync: enableUserSync, + EnableGroupSync: enableGroupSync, + RejectNonProvisionedUsers: rejectNonProvisionedUsers, }, nil } diff --git a/pkg/services/scimutil/scim_util_test.go b/pkg/services/scimutil/scim_util_test.go index 0e9d3b39ee5..f1350402e85 100644 --- a/pkg/services/scimutil/scim_util_test.go +++ b/pkg/services/scimutil/scim_util_test.go @@ -213,33 +213,33 @@ func TestSCIMUtil_IsUserSyncEnabled(t *testing.T) { } } -func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { +func TestSCIMUtil_AreNonProvisionedUsersRejected(t *testing.T) { ctx := context.Background() orgID := int64(1) tests := []struct { name string k8sClient client.K8sHandler - staticAllowed bool + staticRejected bool expectedResult bool setupMock func(*MockK8sHandler) }{ { name: "k8s client nil - returns static config", k8sClient: nil, - staticAllowed: true, + staticRejected: true, expectedResult: true, }, { name: "k8s client nil - returns static config false", k8sClient: nil, - staticAllowed: false, + staticRejected: false, expectedResult: false, }, { - name: "k8s client error - falls back to static config", - k8sClient: &MockK8sHandler{}, - staticAllowed: true, + name: "k8s client error - falls back to static config", + k8sClient: &MockK8sHandler{}, + staticRejected: true, setupMock: func(mockHandler *MockK8sHandler) { mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything). Return(nil, errors.New("k8s error")) @@ -247,9 +247,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { expectedResult: true, }, { - name: "dynamic config user sync enabled - non-provisioned users allowed", - k8sClient: &MockK8sHandler{}, - staticAllowed: false, + name: "dynamic config user sync enabled - non-provisioned users rejected", + k8sClient: &MockK8sHandler{}, + staticRejected: false, setupMock: func(mockHandler *MockK8sHandler) { obj := createMockSCIMConfigWithNonProvisioned(true, false, true) mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything). @@ -258,9 +258,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { expectedResult: true, }, { - name: "dynamic config user sync disabled - non-provisioned users not allowed", - k8sClient: &MockK8sHandler{}, - staticAllowed: true, + name: "dynamic config user sync disabled - non-provisioned users allowed", + k8sClient: &MockK8sHandler{}, + staticRejected: true, setupMock: func(mockHandler *MockK8sHandler) { obj := createMockSCIMConfigWithNonProvisioned(false, true, false) mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything). @@ -269,9 +269,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { expectedResult: false, }, { - name: "dynamic config both settings disabled - non-provisioned users not allowed", - k8sClient: &MockK8sHandler{}, - staticAllowed: true, + name: "dynamic config both settings disabled - non-provisioned users allowed", + k8sClient: &MockK8sHandler{}, + staticRejected: true, setupMock: func(mockHandler *MockK8sHandler) { obj := createMockSCIMConfigWithNonProvisioned(false, false, false) mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything). @@ -280,9 +280,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { expectedResult: false, }, { - name: "dynamic config both settings enabled - non-provisioned users allowed", - k8sClient: &MockK8sHandler{}, - staticAllowed: false, + name: "dynamic config both settings enabled - non-provisioned users rejected", + k8sClient: &MockK8sHandler{}, + staticRejected: false, setupMock: func(mockHandler *MockK8sHandler) { obj := createMockSCIMConfigWithNonProvisioned(true, true, true) mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything). @@ -299,7 +299,7 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) { } util := NewSCIMUtil(tt.k8sClient) - result := util.AreNonProvisionedUsersAllowed(ctx, orgID, tt.staticAllowed) + result := util.AreNonProvisionedUsersRejected(ctx, orgID, tt.staticRejected) assert.Equal(t, tt.expectedResult, result) @@ -449,9 +449,9 @@ func TestSCIMUtil_fetchDynamicSCIMSetting(t *testing.T) { }, }, { - name: "allowNonProvisionedUsers setting enabled", + name: "rejectNonProvisionedUsers setting enabled", k8sClient: &MockK8sHandler{}, - settingType: "allowNonProvisionedUsers", + settingType: "rejectNonProvisionedUsers", expectedEnabled: true, expectedDynamicFetched: true, setupMock: func(mockHandler *MockK8sHandler) { @@ -461,9 +461,9 @@ func TestSCIMUtil_fetchDynamicSCIMSetting(t *testing.T) { }, }, { - name: "allowNonProvisionedUsers setting disabled", + name: "rejectNonProvisionedUsers setting disabled", k8sClient: &MockK8sHandler{}, - settingType: "allowNonProvisionedUsers", + settingType: "rejectNonProvisionedUsers", expectedEnabled: false, expectedDynamicFetched: true, setupMock: func(mockHandler *MockK8sHandler) { @@ -570,36 +570,36 @@ func TestSCIMUtil_unstructuredToSCIMConfig(t *testing.T) { name: "valid object with both settings enabled", obj: createMockSCIMConfig(true, true), expectedSpec: SCIMConfigSpec{ - EnableUserSync: true, - EnableGroupSync: true, - AllowNonProvisionedUsers: false, + EnableUserSync: true, + EnableGroupSync: true, + RejectNonProvisionedUsers: false, }, }, { name: "valid object with both settings disabled", obj: createMockSCIMConfig(false, false), expectedSpec: SCIMConfigSpec{ - EnableUserSync: false, - EnableGroupSync: false, - AllowNonProvisionedUsers: false, + EnableUserSync: false, + EnableGroupSync: false, + RejectNonProvisionedUsers: false, }, }, { name: "valid object with mixed settings", obj: createMockSCIMConfig(true, false), expectedSpec: SCIMConfigSpec{ - EnableUserSync: true, - EnableGroupSync: false, - AllowNonProvisionedUsers: false, + EnableUserSync: true, + EnableGroupSync: false, + RejectNonProvisionedUsers: false, }, }, { - name: "valid object with allowNonProvisionedUsers enabled", + name: "valid object with rejectNonProvisionedUsers enabled", obj: createMockSCIMConfigWithNonProvisioned(false, false, true), expectedSpec: SCIMConfigSpec{ - EnableUserSync: false, - EnableGroupSync: false, - AllowNonProvisionedUsers: true, + EnableUserSync: false, + EnableGroupSync: false, + RejectNonProvisionedUsers: true, }, }, { @@ -641,7 +641,7 @@ func createMockSCIMConfig(userSyncEnabled, groupSyncEnabled bool) *unstructured. } // Helper function to create a mock SCIMConfig unstructured object with non-provisioned users setting -func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, allowNonProvisionedUsers bool) *unstructured.Unstructured { +func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, rejectNonProvisionedUsers bool) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "scim.grafana.com/v0alpha1", @@ -651,9 +651,9 @@ func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, a "namespace": "default", }, "spec": map[string]interface{}{ - "enableUserSync": userSyncEnabled, - "enableGroupSync": groupSyncEnabled, - "allowNonProvisionedUsers": allowNonProvisionedUsers, + "enableUserSync": userSyncEnabled, + "enableGroupSync": groupSyncEnabled, + "rejectNonProvisionedUsers": rejectNonProvisionedUsers, }, }, } @@ -676,9 +676,9 @@ func TestSCIMUtil_Integration(t *testing.T) { userSyncEnabled := util.IsUserSyncEnabled(ctx, orgID, false) assert.True(t, userSyncEnabled) - // Test non-provisioned users allowed - nonProvisionedAllowed := util.AreNonProvisionedUsersAllowed(ctx, orgID, false) - assert.True(t, nonProvisionedAllowed) + // Test non-provisioned users rejected + nonProvisionedRejected := util.AreNonProvisionedUsersRejected(ctx, orgID, false) + assert.True(t, nonProvisionedRejected) mockClient.AssertExpectations(t) }) @@ -695,8 +695,8 @@ func TestSCIMUtil_Integration(t *testing.T) { assert.True(t, userSyncEnabled) // Test non-provisioned users falls back to static config - nonProvisionedAllowed := util.AreNonProvisionedUsersAllowed(ctx, orgID, true) - assert.True(t, nonProvisionedAllowed) + nonProvisionedRejected := util.AreNonProvisionedUsersRejected(ctx, orgID, true) + assert.True(t, nonProvisionedRejected) mockClient.AssertExpectations(t) }) diff --git a/pkg/services/sqlstore/migrations/cloud_migrations.go b/pkg/services/sqlstore/migrations/cloud_migrations.go index 18dc3590570..7a602bf7d42 100644 --- a/pkg/services/sqlstore/migrations/cloud_migrations.go +++ b/pkg/services/sqlstore/migrations/cloud_migrations.go @@ -98,6 +98,15 @@ func addCloudMigrationsMigrations(mg *Migrator) { {Cols: []string{"uid"}, Type: UniqueIndex}, }, } + migrationSnapshotPartitionTable := Table{ + Name: "cloud_migration_snapshot_partition", + Columns: []*Column{ + {Name: "snapshot_uid", Type: DB_NVarchar, Length: 40, Nullable: false}, + {Name: "partition_number", Type: DB_Int, Nullable: false}, + {Name: "resource_type", Type: DB_Varchar, Length: 255, Nullable: false}, + {Name: "data", Type: DB_LongBlob, Nullable: false}, + }, + } addTableReplaceMigrations(mg, migrationTable, migrationSessionTable, 2, map[string]string{ "id": "id", @@ -187,4 +196,32 @@ func addCloudMigrationsMigrations(mg *Migrator) { mg.AddMigration("increase resource_uid column length", NewRawSQLMigration(""). Mysql("ALTER TABLE cloud_migration_resource MODIFY resource_uid NVARCHAR(255);"). Postgres("ALTER TABLE cloud_migration_resource ALTER COLUMN resource_uid TYPE VARCHAR(255);")) + + mg.AddMigration("create cloud_migration_snapshot_partition table v1", NewAddTableMigration(migrationSnapshotPartitionTable)) + mg.AddMigration("add cloud_migration_snapshot_partition srp_unique index", NewAddIndexMigration(migrationSnapshotPartitionTable, &Index{ + Name: "srp_unique", + Cols: []string{"snapshot_uid", "resource_type", "partition_number"}, Type: UniqueIndex, + })) + mg.AddMigration("add resource_storage_type column to cloud_migration_snapshot table", NewAddColumnMigration(migrationSnapshotTable, &Column{ + Name: "resource_storage_type", + Type: DB_Varchar, + Length: 255, + Nullable: true, + })) + mg.AddMigration("add encryption_algo column to cloud_migration_snapshot table", NewAddColumnMigration(migrationSnapshotTable, &Column{ + Name: "encryption_algo", + Type: DB_Varchar, + Length: 255, + Nullable: true, + })) + mg.AddMigration("add metadata column to cloud_migration_snapshot table", NewAddColumnMigration(migrationSnapshotTable, &Column{ + Name: "metadata", + Type: DB_Blob, + Nullable: true, + })) + mg.AddMigration("add public_key column to cloud_migration_snapshot table", NewAddColumnMigration(migrationSnapshotTable, &Column{ + Name: "public_key", + Type: DB_Blob, + Nullable: true, + })) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index f14dbc03fc7..32f7be6830b 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -576,6 +576,7 @@ type Cfg struct { MemberlistJoinMember string MemberlistClusterLabel string MemberlistClusterLabelVerificationDisabled bool + SearchRingReplicationFactor int InstanceID string SprinklesApiServer string SprinklesApiServerPageLimit int diff --git a/pkg/setting/setting_cloud_migration.go b/pkg/setting/setting_cloud_migration.go index dce1ac5fa7c..87f549ca9cd 100644 --- a/pkg/setting/setting_cloud_migration.go +++ b/pkg/setting/setting_cloud_migration.go @@ -14,11 +14,12 @@ const ( ) type CloudMigrationSettings struct { - IsTarget bool GcomAPIToken string AuthAPIUrl string SnapshotFolder string GMSDomain string + AlertRulesState string + ResourceStorageType string GMSStartSnapshotTimeout time.Duration GMSGetSnapshotStatusTimeout time.Duration GMSCreateUploadUrlTimeout time.Duration @@ -33,8 +34,8 @@ type CloudMigrationSettings struct { DeleteTokenTimeout time.Duration TokenExpiresAfter time.Duration FrontendPollInterval time.Duration - AlertRulesState string + IsTarget bool IsDeveloperMode bool } @@ -45,6 +46,8 @@ func (cfg *Cfg) readCloudMigrationSettings() { cfg.CloudMigration.AuthAPIUrl = cloudMigration.Key("auth_api_url").MustString("") cfg.CloudMigration.SnapshotFolder = cloudMigration.Key("snapshot_folder").MustString("") cfg.CloudMigration.GMSDomain = cloudMigration.Key("domain").MustString("") + cfg.CloudMigration.AlertRulesState = cloudMigration.Key("alert_rules_state").In(GMSAlertRulesPaused, []string{GMSAlertRulesPaused, GMSAlertRulesUnchanged}) + cfg.CloudMigration.ResourceStorageType = cloudMigration.Key("resource_storage_type").In("db", []string{"db", "fs"}) cfg.CloudMigration.GMSValidateKeyTimeout = cloudMigration.Key("validate_key_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.GMSStartSnapshotTimeout = cloudMigration.Key("start_snapshot_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.GMSGetSnapshotStatusTimeout = cloudMigration.Key("get_snapshot_status_timeout").MustDuration(5 * time.Second) @@ -60,7 +63,6 @@ func (cfg *Cfg) readCloudMigrationSettings() { cfg.CloudMigration.TokenExpiresAfter = cloudMigration.Key("token_expires_after").MustDuration(7 * 24 * time.Hour) cfg.CloudMigration.IsDeveloperMode = cloudMigration.Key("developer_mode").MustBool(false) cfg.CloudMigration.FrontendPollInterval = cloudMigration.Key("frontend_poll_interval").MustDuration(2 * time.Second) - cfg.CloudMigration.AlertRulesState = cloudMigration.Key("alert_rules_state").In(GMSAlertRulesPaused, []string{GMSAlertRulesPaused, GMSAlertRulesUnchanged}) if cfg.CloudMigration.SnapshotFolder == "" { cfg.CloudMigration.SnapshotFolder = filepath.Join(cfg.DataPath, "cloud_migration") diff --git a/pkg/setting/setting_openfeature.go b/pkg/setting/setting_openfeature.go index 13131f89111..a817dc28a80 100644 --- a/pkg/setting/setting_openfeature.go +++ b/pkg/setting/setting_openfeature.go @@ -11,6 +11,7 @@ const ( ) type OpenFeatureSettings struct { + APIEnabled bool ProviderType string URL *url.URL TargetingKey string @@ -21,6 +22,7 @@ func (cfg *Cfg) readOpenFeatureSettings() error { cfg.OpenFeature = OpenFeatureSettings{} config := cfg.Raw.Section("feature_toggles.openfeature") + cfg.OpenFeature.APIEnabled = config.Key("enable_api").MustBool(true) cfg.OpenFeature.ProviderType = config.Key("provider").MustString(StaticProviderType) cfg.OpenFeature.TargetingKey = config.Key("targetingKey").MustString(cfg.AppURL) diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index f0881cf95db..2e3a4a3122b 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -65,6 +65,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.MemberlistJoinMember = section.Key("memberlist_join_member").String() cfg.MemberlistClusterLabel = section.Key("memberlist_cluster_label").String() cfg.MemberlistClusterLabelVerificationDisabled = section.Key("memberlist_cluster_label_verification_disabled").MustBool(false) + cfg.SearchRingReplicationFactor = section.Key("search_ring_replication_factor").MustInt(1) cfg.InstanceID = section.Key("instance_id").String() cfg.IndexFileThreshold = section.Key("index_file_threshold").MustInt(10) cfg.IndexMinCount = section.Key("index_min_count").MustInt(1) diff --git a/pkg/storage/secret/encryption/data/data_key_disable_all.sql b/pkg/storage/secret/encryption/data/data_key_disable_all.sql new file mode 100644 index 00000000000..92a2bd6a5c6 --- /dev/null +++ b/pkg/storage/secret/encryption/data/data_key_disable_all.sql @@ -0,0 +1,7 @@ +UPDATE + {{ .Ident "secret_data_key" }} +SET + {{ .Ident "active" }} = false, + {{ .Ident "updated" }} = {{ .Arg .Updated }} +WHERE {{ .Ident "active" }} = true +; diff --git a/pkg/storage/secret/encryption/data_key_store.go b/pkg/storage/secret/encryption/data_key_store.go index aa94333b629..6d38d117901 100644 --- a/pkg/storage/secret/encryption/data_key_store.go +++ b/pkg/storage/secret/encryption/data_key_store.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) @@ -37,11 +38,16 @@ func ProvideDataKeyStorage( } func (ss *encryptionStoreImpl) GetDataKey(ctx context.Context, namespace, uid string) (*contracts.SecretDataKey, error) { + start := time.Now() ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.GetDataKey", trace.WithAttributes( attribute.String("namespace", namespace), attribute.String("uid", uid), )) - defer span.End() + + defer func() { + span.End() + ss.metrics.GetDataKeyDuration.Observe(float64(time.Since(start))) + }() req := readDataKey{ SQLTemplate: sqltemplate.New(ss.dialect), @@ -95,11 +101,15 @@ func (ss *encryptionStoreImpl) GetDataKey(ctx context.Context, namespace, uid st } func (ss *encryptionStoreImpl) GetCurrentDataKey(ctx context.Context, namespace, label string) (*contracts.SecretDataKey, error) { + start := time.Now() ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.GetCurrentDataKey", trace.WithAttributes( attribute.String("namespace", namespace), attribute.String("label", label), )) - defer span.End() + defer func() { + span.End() + ss.metrics.GetCurrentDataKeyDuration.Observe(float64(time.Since(start))) + }() req := readCurrentDataKey{ SQLTemplate: sqltemplate.New(ss.dialect), @@ -152,11 +162,15 @@ func (ss *encryptionStoreImpl) GetCurrentDataKey(ctx context.Context, namespace, }, nil } -func (ss *encryptionStoreImpl) GetAllDataKeys(ctx context.Context, namespace string) ([]*contracts.SecretDataKey, error) { - ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.GetAllDataKeys", trace.WithAttributes( +func (ss *encryptionStoreImpl) ListDataKeys(ctx context.Context, namespace string) ([]*contracts.SecretDataKey, error) { + start := time.Now() + ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.ListDataKeys", trace.WithAttributes( attribute.String("namespace", namespace), )) - defer span.End() + defer func() { + span.End() + ss.metrics.ListDataKeysDuration.Observe(float64(time.Since(start))) + }() req := listDataKeys{ SQLTemplate: sqltemplate.New(ss.dialect), @@ -210,12 +224,16 @@ func (ss *encryptionStoreImpl) GetAllDataKeys(ctx context.Context, namespace str } func (ss *encryptionStoreImpl) CreateDataKey(ctx context.Context, dataKey *contracts.SecretDataKey) error { + start := time.Now() ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.CreateDataKey", trace.WithAttributes( attribute.String("uid", dataKey.UID), attribute.String("namespace", dataKey.Namespace), attribute.Bool("active", dataKey.Active), )) - defer span.End() + defer func() { + span.End() + ss.metrics.CreateDataKeyDuration.Observe(float64(time.Since(start))) + }() if !dataKey.Active { return fmt.Errorf("cannot insert deactivated data keys") @@ -252,10 +270,14 @@ func (ss *encryptionStoreImpl) CreateDataKey(ctx context.Context, dataKey *contr } func (ss *encryptionStoreImpl) DisableDataKeys(ctx context.Context, namespace string) error { + start := time.Now() ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.DisableDataKeys", trace.WithAttributes( attribute.String("namespace", namespace), )) - defer span.End() + defer func() { + span.End() + ss.metrics.DisableDataKeysDuration.Observe(float64(time.Since(start))) + }() req := disableDataKeys{ SQLTemplate: sqltemplate.New(ss.dialect), @@ -278,19 +300,23 @@ func (ss *encryptionStoreImpl) DisableDataKeys(ctx context.Context, namespace st return fmt.Errorf("getting rows affected: %w", err) } - if rowsAffected != 1 { - return fmt.Errorf("expected 1 row affected, but affected %d", rowsAffected) + if rowsAffected == 0 { + logging.FromContext(ctx).Info("Disable all data keys: no keys were disabled for namespace", "namespace", namespace) } return nil } func (ss *encryptionStoreImpl) DeleteDataKey(ctx context.Context, namespace, uid string) error { + start := time.Now() ctx, span := ss.tracer.Start(ctx, "DataKeyStorage.DeleteDataKey", trace.WithAttributes( attribute.String("uid", uid), attribute.String("namespace", namespace), )) - defer span.End() + defer func() { + span.End() + ss.metrics.DeleteDataKeyDuration.Observe(float64(time.Since(start))) + }() if len(uid) == 0 { return fmt.Errorf("data key id is missing") @@ -323,3 +349,60 @@ func (ss *encryptionStoreImpl) DeleteDataKey(ctx context.Context, namespace, uid return nil } + +type globalEncryptionStoreImpl struct { + db contracts.Database + dialect sqltemplate.Dialect + tracer trace.Tracer + metrics *GlobalDataKeyMetrics +} + +func ProvideGlobalDataKeyStorage( + db contracts.Database, + tracer trace.Tracer, + registerer prometheus.Registerer, +) (contracts.GlobalDataKeyStorage, error) { + store := &globalEncryptionStoreImpl{ + db: db, + dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, + metrics: NewGlobalDataKeyMetrics(registerer), + } + + return store, nil +} + +func (ss *globalEncryptionStoreImpl) DisableAllDataKeys(ctx context.Context) error { + start := time.Now() + ctx, span := ss.tracer.Start(ctx, "GlobalDataKeyStorage.DisableAllDataKeys") + defer func() { + span.End() + ss.metrics.DisableAllDataKeysDuration.Observe(float64(time.Since(start))) + }() + + req := disableAllDataKeys{ + SQLTemplate: sqltemplate.New(ss.dialect), + Updated: time.Now(), + } + + query, err := sqltemplate.Execute(sqlDataKeyDisableAll, req) + if err != nil { + return fmt.Errorf("execute template %q: %w", sqlDataKeyDisableAll.Name(), err) + } + + result, err := ss.db.ExecContext(ctx, query, req.GetArgs()...) + if err != nil { + return fmt.Errorf("updating data keys: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("getting rows affected: %w", err) + } + + if rowsAffected == 0 { + logging.FromContext(ctx).Info("Disable all data keys: no keys were disabled") + } + + return nil +} diff --git a/pkg/storage/secret/encryption/data_key_store_test.go b/pkg/storage/secret/encryption/data_key_store_test.go index 134be881231..18081997ad5 100644 --- a/pkg/storage/secret/encryption/data_key_store_test.go +++ b/pkg/storage/secret/encryption/data_key_store_test.go @@ -31,6 +31,8 @@ func TestEncryptionStoreImpl_DataKeyLifecycle(t *testing.T) { tracer := noop.NewTracerProvider().Tracer("test") store, err := ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, nil) require.NoError(t, err) + globalStore, err := ProvideGlobalDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, nil) + require.NoError(t, err) ctx := context.Background() @@ -71,8 +73,8 @@ func TestEncryptionStoreImpl_DataKeyLifecycle(t *testing.T) { require.Equal(t, dataKey.UID, currentKey.UID) require.Equal(t, dataKey.Namespace, currentKey.Namespace) - // Test GetAllDataKeys - allKeys, err := store.GetAllDataKeys(ctx, "test-namespace") + // Test ListDataKeys + allKeys, err := store.ListDataKeys(ctx, "test-namespace") require.NoError(t, err) require.Len(t, allKeys, 1) require.Equal(t, dataKey.UID, allKeys[0].UID) @@ -101,6 +103,15 @@ func TestEncryptionStoreImpl_DataKeyLifecycle(t *testing.T) { require.Equal(t, unchangingDataKey.UID, staticKey.UID) require.Equal(t, unchangingDataKey.Namespace, staticKey.Namespace) require.True(t, staticKey.Active) + + // Test DisableAllDataKeys + err = globalStore.DisableAllDataKeys(ctx) + require.NoError(t, err) + + // Verify that remaining data keys are disabled + disabledKey, err = store.GetDataKey(ctx, "static-namespace", "static-uid") + require.NoError(t, err) + require.False(t, disabledKey.Active) } type PassThroughEncryptionProvider struct{} diff --git a/pkg/storage/secret/encryption/metrics.go b/pkg/storage/secret/encryption/metrics.go index a7a116541ce..5262139cdd5 100644 --- a/pkg/storage/secret/encryption/metrics.go +++ b/pkg/storage/secret/encryption/metrics.go @@ -14,10 +14,9 @@ type DataKeyMetrics struct { CreateDataKeyDuration prometheus.Histogram GetDataKeyDuration prometheus.Histogram GetCurrentDataKeyDuration prometheus.Histogram - GetAllDataKeysDuration prometheus.Histogram + ListDataKeysDuration prometheus.Histogram DisableDataKeysDuration prometheus.Histogram DeleteDataKeyDuration prometheus.Histogram - ReEncryptDataKeysDuration prometheus.Histogram } func newDataKeyMetrics() *DataKeyMetrics { @@ -43,11 +42,11 @@ func newDataKeyMetrics() *DataKeyMetrics { Help: "Duration of get current data key operations", Buckets: prometheus.DefBuckets, }), - GetAllDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ + ListDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, - Name: "get_all_data_keys_duration_seconds", - Help: "Duration of get all data keys operations", + Name: "list_data_keys_duration_seconds", + Help: "Duration of list data keys operations", Buckets: prometheus.DefBuckets, }), DisableDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ @@ -64,17 +63,11 @@ func newDataKeyMetrics() *DataKeyMetrics { Help: "Duration of delete data key operations", Buckets: prometheus.DefBuckets, }), - ReEncryptDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "re_encrypt_data_keys_duration_seconds", - Help: "Duration of re-encrypt data keys operations", - Buckets: prometheus.DefBuckets, - }), } } -// NewDataKeyMetrics returns a singleton instance of the SecretsMetrics struct containing registered metrics +// NewDataKeyMetrics returns an instance of the DataKeyMetrics +// struct containing registered metrics if [reg] is not nil. func NewDataKeyMetrics(reg prometheus.Registerer) *DataKeyMetrics { m := newDataKeyMetrics() @@ -83,16 +76,42 @@ func NewDataKeyMetrics(reg prometheus.Registerer) *DataKeyMetrics { m.CreateDataKeyDuration, m.GetDataKeyDuration, m.GetCurrentDataKeyDuration, - m.GetAllDataKeysDuration, + m.ListDataKeysDuration, m.DisableDataKeysDuration, m.DeleteDataKeyDuration, - m.ReEncryptDataKeysDuration, ) } return m } -func NewTestMetrics() *DataKeyMetrics { - return newDataKeyMetrics() +type GlobalDataKeyMetrics struct { + DisableAllDataKeysDuration prometheus.Histogram +} + +func newGlobalDataKeyMetrics() *GlobalDataKeyMetrics { + return &GlobalDataKeyMetrics{ + + DisableAllDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "disable_all_data_keys_duration_seconds", + Help: "Duration of disable all data keys operations", + Buckets: prometheus.DefBuckets, + }), + } +} + +// NewGlobalDataKeyMetrics returns an instance of the GlobalDataKeyMetrics +// struct containing registered metrics if [reg] is not nil. +func NewGlobalDataKeyMetrics(reg prometheus.Registerer) *GlobalDataKeyMetrics { + m := newGlobalDataKeyMetrics() + + if reg != nil { + reg.MustRegister( + m.DisableAllDataKeysDuration, + ) + } + + return m } diff --git a/pkg/storage/secret/encryption/query.go b/pkg/storage/secret/encryption/query.go index 25f21cb77ba..573e295dcaa 100644 --- a/pkg/storage/secret/encryption/query.go +++ b/pkg/storage/secret/encryption/query.go @@ -28,6 +28,7 @@ var ( sqlDataKeyList = mustTemplate("data_key_list.sql") sqlDataKeyDisable = mustTemplate("data_key_disable.sql") sqlDataKeyDelete = mustTemplate("data_key_delete.sql") + sqlDataKeyDisableAll = mustTemplate("data_key_disable_all.sql") ) // TODO: Move this to a common place so that all stores can use @@ -140,3 +141,10 @@ type deleteDataKey struct { } func (r deleteDataKey) Validate() error { return nil } + +type disableAllDataKeys struct { + sqltemplate.SQLTemplate + Updated time.Time +} + +func (r disableAllDataKeys) Validate() error { return nil } diff --git a/pkg/storage/secret/encryption/query_test.go b/pkg/storage/secret/encryption/query_test.go index 51e078d8e25..c2ebf7a9635 100644 --- a/pkg/storage/secret/encryption/query_test.go +++ b/pkg/storage/secret/encryption/query_test.go @@ -159,6 +159,15 @@ func TestDataKeyQueries(t *testing.T) { }, }, }, + sqlDataKeyDisableAll: { + { + Name: "disable", + Data: &disableAllDataKeys{ + SQLTemplate: mocks.NewTestingSQLTemplate(), + Updated: time.Unix(1735689600, 0).UTC(), + }, + }, + }, }, }) } diff --git a/pkg/storage/secret/encryption/testdata/mysql--data_key_disable_all-disable.sql b/pkg/storage/secret/encryption/testdata/mysql--data_key_disable_all-disable.sql new file mode 100755 index 00000000000..49972ef5026 --- /dev/null +++ b/pkg/storage/secret/encryption/testdata/mysql--data_key_disable_all-disable.sql @@ -0,0 +1,7 @@ +UPDATE + `secret_data_key` +SET + `active` = false, + `updated` = '2025-01-01 00:00:00 +0000 UTC' +WHERE `active` = true +; diff --git a/pkg/storage/secret/encryption/testdata/postgres--data_key_disable_all-disable.sql b/pkg/storage/secret/encryption/testdata/postgres--data_key_disable_all-disable.sql new file mode 100755 index 00000000000..8b72ac271fc --- /dev/null +++ b/pkg/storage/secret/encryption/testdata/postgres--data_key_disable_all-disable.sql @@ -0,0 +1,7 @@ +UPDATE + "secret_data_key" +SET + "active" = false, + "updated" = '2025-01-01 00:00:00 +0000 UTC' +WHERE "active" = true +; diff --git a/pkg/storage/secret/encryption/testdata/sqlite--data_key_disable_all-disable.sql b/pkg/storage/secret/encryption/testdata/sqlite--data_key_disable_all-disable.sql new file mode 100755 index 00000000000..8b72ac271fc --- /dev/null +++ b/pkg/storage/secret/encryption/testdata/sqlite--data_key_disable_all-disable.sql @@ -0,0 +1,7 @@ +UPDATE + "secret_data_key" +SET + "active" = false, + "updated" = '2025-01-01 00:00:00 +0000 UTC' +WHERE "active" = true +; diff --git a/pkg/storage/secret/metadata/metrics/metrics.go b/pkg/storage/secret/metadata/metrics/metrics.go index bedaa03b4f8..aea3817f12c 100644 --- a/pkg/storage/secret/metadata/metrics/metrics.go +++ b/pkg/storage/secret/metadata/metrics/metrics.go @@ -35,7 +35,6 @@ type StorageMetrics struct { SecureValueMetadataGetCount prometheus.Counter SecureValueMetadataListDuration prometheus.Histogram SecureValueMetadataListCount prometheus.Counter - SecureValueGetForDecryptDuration prometheus.Histogram SecureValueSetExternalIDDuration prometheus.Histogram SecureValueSetStatusDuration prometheus.Histogram @@ -185,13 +184,6 @@ func newStorageMetrics() *StorageMetrics { Name: "secure_value_metadata_list_count", Help: "Count of secure value metadata list operations", }), - SecureValueGetForDecryptDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "secure_value_get_for_decrypt_duration_seconds", - Help: "Duration of secure value get for decrypt operations", - Buckets: prometheus.DefBuckets, - }), SecureValueSetExternalIDDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, @@ -257,7 +249,6 @@ func NewStorageMetrics(reg prometheus.Registerer) *StorageMetrics { m.SecureValueMetadataGetCount, m.SecureValueMetadataListDuration, m.SecureValueMetadataListCount, - m.SecureValueGetForDecryptDuration, m.SecureValueSetExternalIDDuration, m.SecureValueSetStatusDuration, m.DecryptDuration, diff --git a/pkg/storage/secret/metadata/secure_value_test.go b/pkg/storage/secret/metadata/secure_value_test.go index 856c1dfc021..9400735cdd1 100644 --- a/pkg/storage/secret/metadata/secure_value_test.go +++ b/pkg/storage/secret/metadata/secure_value_test.go @@ -7,7 +7,6 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/mitchellh/copystructure" @@ -111,24 +110,24 @@ func (m *model) list(namespace string) (*secretv1beta1.SecureValueList, error) { return &secretv1beta1.SecureValueList{Items: out}, nil } -func (m *model) decrypt(decrypter, namespace, name string) (map[string]service.DecryptResult, error) { +func (m *model) decrypt(decrypter, namespace, name string) (map[string]contracts.DecryptResult, error) { for _, v := range m.secureValues { if v.Namespace == namespace && v.Name == name && v.active { if slices.ContainsFunc(v.Spec.Decrypters, func(d string) bool { return d == decrypter }) { - return map[string]service.DecryptResult{ - name: service.NewDecryptResultValue(deepCopy(v).Spec.Value), + return map[string]contracts.DecryptResult{ + name: contracts.NewDecryptResultValue(deepCopy(v).Spec.Value), }, nil } - return map[string]service.DecryptResult{ - name: service.NewDecryptResultErr(contracts.ErrDecryptNotAuthorized), + return map[string]contracts.DecryptResult{ + name: contracts.NewDecryptResultErr(contracts.ErrDecryptNotAuthorized), }, nil } } - return map[string]service.DecryptResult{ - name: service.NewDecryptResultErr(contracts.ErrDecryptNotFound), + return map[string]contracts.DecryptResult{ + name: contracts.NewDecryptResultErr(contracts.ErrDecryptNotFound), }, nil } diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index 0a88d9a035a..96a44f6d3c3 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -4,6 +4,7 @@ import ( "cmp" "context" "fmt" + "hash/fnv" "log/slog" "slices" "strings" @@ -19,6 +20,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/grafana/authlib/types" + "github.com/grafana/dskit/ring" dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" @@ -109,6 +111,9 @@ type searchSupport struct { initMinSize int initMaxSize int + ring *ring.Ring + ringLifecycler *ring.BasicLifecycler + buildIndex singleflight.Group // Index queue processors @@ -128,7 +133,7 @@ var ( _ resourcepb.ManagedObjectIndexServer = (*searchSupport)(nil) ) -func newSearchSupport(opts SearchOptions, storage StorageBackend, access types.AccessClient, blob BlobSupport, tracer trace.Tracer, indexMetrics *BleveIndexMetrics) (support *searchSupport, err error) { +func newSearchSupport(opts SearchOptions, storage StorageBackend, access types.AccessClient, blob BlobSupport, tracer trace.Tracer, indexMetrics *BleveIndexMetrics, ring *ring.Ring, ringLifecycler *ring.BasicLifecycler) (support *searchSupport, err error) { // No backend search support if opts.Backend == nil { return nil, nil @@ -155,6 +160,8 @@ func newSearchSupport(opts SearchOptions, storage StorageBackend, access types.A indexEventsChan: make(chan *IndexEvent), indexQueueProcessors: make(map[string]*indexQueueProcessor), rebuildInterval: opts.RebuildInterval, + ring: ring, + ringLifecycler: ringLifecycler, } info, err := opts.Resources.GetDocumentBuilders() @@ -379,6 +386,33 @@ func (s *searchSupport) GetStats(ctx context.Context, req *resourcepb.ResourceSt return rsp, nil } +func (s *searchSupport) shouldBuildIndex(info ResourceStats) bool { + if s.ring == nil { + s.log.Debug("ring is not setup. Will proceed to build index") + return true + } + + if s.ringLifecycler == nil { + s.log.Error("missing ring lifecycler") + return true + } + + ringHasher := fnv.New32a() + _, err := ringHasher.Write([]byte(info.Namespace)) + if err != nil { + s.log.Error("error hashing namespace", "namespace", info.Namespace, "err", err) + return true + } + + rs, err := s.ring.GetWithOptions(ringHasher.Sum32(), searchOwnerRead, ring.WithReplicationFactor(s.ring.ReplicationFactor())) + if err != nil { + s.log.Error("error getting replicaset from ring", "namespace", info.Namespace, "err", err) + return true + } + + return rs.Includes(s.ringLifecycler.GetInstanceAddr()) +} + func (s *searchSupport) buildIndexes(ctx context.Context, rebuild bool) (int, error) { totalBatchesIndexed := 0 group := errgroup.Group{} @@ -395,6 +429,11 @@ func (s *searchSupport) buildIndexes(ctx context.Context, rebuild bool) (int, er continue } + if !s.shouldBuildIndex(info) { + s.log.Debug("skip building index", "namespace", info.Namespace, "group", info.Group, "resource", info.Resource) + continue + } + group.Go(func() error { if rebuild { // we need to clear the cache to make sure we get the latest usage insights data diff --git a/pkg/storage/unified/resource/search_client.go b/pkg/storage/unified/resource/search_client.go index 5679711e9e3..e0e36bcb51c 100644 --- a/pkg/storage/unified/resource/search_client.go +++ b/pkg/storage/unified/resource/search_client.go @@ -2,33 +2,36 @@ package resource import ( "context" + "time" "google.golang.org/grpc" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) +const ( + // backgroundRequestTimeout is the timeout for background shadow traffic requests + backgroundRequestTimeout = 500 * time.Millisecond +) + type DualWriter interface { IsEnabled(schema.GroupResource) bool ReadFromUnified(context.Context, schema.GroupResource) (bool, error) } func NewSearchClient(dual DualWriter, gr schema.GroupResource, unifiedClient resourcepb.ResourceIndexClient, - legacyClient resourcepb.ResourceIndexClient) resourcepb.ResourceIndexClient { - if dual.IsEnabled(gr) { - return &searchWrapper{ - dual: dual, - groupResource: gr, - unifiedClient: unifiedClient, - legacyClient: legacyClient, - } + legacyClient resourcepb.ResourceIndexClient, features featuremgmt.FeatureToggles) resourcepb.ResourceIndexClient { + return &searchWrapper{ + dual: dual, + groupResource: gr, + unifiedClient: unifiedClient, + legacyClient: legacyClient, + features: features, + logger: log.New("unified-storage.search-client"), } - //nolint:errcheck - if ok, _ := dual.ReadFromUnified(context.Background(), gr); ok { - return unifiedClient - } - return legacyClient } type searchWrapper struct { @@ -37,6 +40,8 @@ type searchWrapper struct { unifiedClient resourcepb.ResourceIndexClient legacyClient resourcepb.ResourceIndexClient + features featuremgmt.FeatureToggles + logger log.Logger } func (s *searchWrapper) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, @@ -49,6 +54,26 @@ func (s *searchWrapper) GetStats(ctx context.Context, in *resourcepb.ResourceSta if unified { client = s.unifiedClient } + + // If dual reader feature flag is enabled, and legacy is the main storage, + // make a background call to unified + if s.features != nil && s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) && !unified { + // Create background context with timeout but ignore parent cancelation + ctxBg := context.WithoutCancel(ctx) + ctxBgWithTimeout, cancel := context.WithTimeout(ctxBg, backgroundRequestTimeout) + + // Make background call without blocking the main request + go func() { + defer cancel() // Ensure we clean up the context + _, bgErr := s.unifiedClient.GetStats(ctxBgWithTimeout, in, opts...) + if bgErr != nil { + s.logger.Error("Background GetStats call to unified failed", "error", bgErr, "timeout", backgroundRequestTimeout) + } else { + s.logger.Debug("Background GetStats call to unified succeeded") + } + }() + } + return client.GetStats(ctx, in, opts...) } @@ -62,5 +87,25 @@ func (s *searchWrapper) Search(ctx context.Context, in *resourcepb.ResourceSearc if unified { client = s.unifiedClient } + + // If dual reader feature flag is enabled, and legacy is the main storage, + // make a background call to unified + if s.features != nil && s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) && !unified { + // Create background context with timeout but ignore parent cancelation + ctxBg := context.WithoutCancel(ctx) + ctxBgWithTimeout, cancel := context.WithTimeout(ctxBg, backgroundRequestTimeout) + + // Make background call without blocking the main request + go func() { + defer cancel() // Ensure we clean up the context + _, bgErr := s.unifiedClient.Search(ctxBgWithTimeout, in, opts...) + if bgErr != nil { + s.logger.Error("Background Search call to unified failed", "error", bgErr, "timeout", backgroundRequestTimeout) + } else { + s.logger.Debug("Background Search call to unified succeeded") + } + }() + } + return client.Search(ctx, in, opts...) } diff --git a/pkg/storage/unified/resource/search_client_test.go b/pkg/storage/unified/resource/search_client_test.go new file mode 100644 index 00000000000..7784dc52e16 --- /dev/null +++ b/pkg/storage/unified/resource/search_client_test.go @@ -0,0 +1,420 @@ +package resource + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/util/testutil" +) + +// Mock DualWriter +type MockDualWriter struct { + mock.Mock +} + +func (m *MockDualWriter) IsEnabled(gr schema.GroupResource) bool { + args := m.Called(gr) + return args.Bool(0) +} + +func (m *MockDualWriter) ReadFromUnified(ctx context.Context, gr schema.GroupResource) (bool, error) { + args := m.Called(ctx, gr) + return args.Bool(0), args.Error(1) +} + +// Mock ResourceIndexClient with enhanced timeout testing capabilities +type MockResourceIndexClient struct { + mock.Mock + searchCalled chan struct{} + statsCalled chan struct{} + searchDelay time.Duration + statsDelay time.Duration + contextCanceled chan context.Context +} + +func NewMockResourceIndexClient() *MockResourceIndexClient { + return &MockResourceIndexClient{ + searchCalled: make(chan struct{}, 1), + statsCalled: make(chan struct{}, 1), + contextCanceled: make(chan context.Context, 10), // Buffer for multiple calls + } +} + +func (m *MockResourceIndexClient) SetSearchDelay(delay time.Duration) { + m.searchDelay = delay +} + +func (m *MockResourceIndexClient) SetStatsDelay(delay time.Duration) { + m.statsDelay = delay +} + +func (m *MockResourceIndexClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) { + args := m.Called(ctx, in, opts) + + // Simulate delay if configured + if m.searchDelay > 0 { + select { + case <-time.After(m.searchDelay): + // Delay completed normally + case <-ctx.Done(): + // Context was canceled during delay + m.contextCanceled <- ctx + return nil, ctx.Err() + } + } + + // Signal that Search was called + select { + case m.searchCalled <- struct{}{}: + default: + } + + return args.Get(0).(*resourcepb.ResourceSearchResponse), args.Error(1) +} + +func (m *MockResourceIndexClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) { + args := m.Called(ctx, in, opts) + + // Simulate delay if configured + if m.statsDelay > 0 { + select { + case <-time.After(m.statsDelay): + // Delay completed normally + case <-ctx.Done(): + // Context was canceled during delay + m.contextCanceled <- ctx + return nil, ctx.Err() + } + } + + // Signal that GetStats was called + select { + case m.statsCalled <- struct{}{}: + default: + } + + return args.Get(0).(*resourcepb.ResourceStatsResponse), args.Error(1) +} + +func setupTestSearchClient(t *testing.T) (schema.GroupResource, *MockResourceIndexClient, *MockResourceIndexClient, featuremgmt.FeatureToggles) { + t.Helper() + gr := schema.GroupResource{Group: "test", Resource: "items"} + unifiedClient := NewMockResourceIndexClient() + legacyClient := NewMockResourceIndexClient() + features := featuremgmt.WithFeatures() + return gr, unifiedClient, legacyClient, features +} + +func setupTestSearchWrapper(t *testing.T, dual *MockDualWriter, unifiedClient, legacyClient *MockResourceIndexClient, features featuremgmt.FeatureToggles, gr schema.GroupResource) *searchWrapper { + t.Helper() + return &searchWrapper{ + dual: dual, + groupResource: gr, + unifiedClient: unifiedClient, + legacyClient: legacyClient, + features: features, + logger: log.NewNopLogger(), + } +} + +func TestSearchClient_NewSearchClient(t *testing.T) { + gr, unifiedClient, legacyClient, features := setupTestSearchClient(t) + + t.Run("always returns wrapper", func(t *testing.T) { + dual := &MockDualWriter{} // Create fresh mock for this test + + client := NewSearchClient(dual, gr, unifiedClient, legacyClient, features) + + wrapper, ok := client.(*searchWrapper) + require.True(t, ok) + assert.Equal(t, dual, wrapper.dual) + assert.Equal(t, gr, wrapper.groupResource) + assert.Equal(t, unifiedClient, wrapper.unifiedClient) + assert.Equal(t, legacyClient, wrapper.legacyClient) + }) +} + +func TestSearchWrapper_Search(t *testing.T) { + gr, unifiedClient, legacyClient, features := setupTestSearchClient(t) + req := &resourcepb.ResourceSearchRequest{Query: "test"} + expectedResponse := &resourcepb.ResourceSearchResponse{TotalHits: 0} + + t.Run("uses unified client when reading from unified", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + + dual.On("ReadFromUnified", mock.Anything, gr).Return(true, nil) + unifiedClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, features, gr) + + resp, err := wrapper.Search(ctx, req) + + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + dual.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + legacyClient.AssertNotCalled(t, "Search") + }) + + t.Run("uses legacy client when not reading from unified", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, features, gr) + + resp, err := wrapper.Search(ctx, req) + + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertNotCalled(t, "Search") + }) + + t.Run("makes background call to unified when feature flag enabled and using legacy", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Expect background call to unified client + unifiedBgResponse := &resourcepb.ResourceSearchResponse{TotalHits: 0} + unifiedClient.On("Search", mock.Anything, req, mock.Anything).Return(unifiedBgResponse, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + resp, err := wrapper.Search(ctx, req) + + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + // Wait for background goroutine to complete + select { + case <-unifiedClient.searchCalled: + // Background call was made + case <-time.After(100 * time.Millisecond): + t.Fatal("Background unified client call was not made within timeout") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) + + t.Run("handles background call error gracefully", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Background call returns error - should be handled gracefully + unifiedClient.On("Search", mock.Anything, req, mock.Anything).Return((*resourcepb.ResourceSearchResponse)(nil), assert.AnError) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + resp, err := wrapper.Search(ctx, req) + + // Main request should still succeed despite background error + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + // Wait for background goroutine to complete + select { + case <-unifiedClient.searchCalled: + // Background call was made (even though it failed) + case <-time.After(100 * time.Millisecond): + t.Fatal("Background unified client call was not made within timeout") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) + + t.Run("background request times out after 500ms", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Configure unified client to take longer than the 500ms timeout + unifiedClient.SetSearchDelay(600 * time.Millisecond) // Longer than 500ms timeout + unifiedClient.On("Search", mock.Anything, req, mock.Anything).Return((*resourcepb.ResourceSearchResponse)(nil), context.DeadlineExceeded) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + start := time.Now() + resp, err := wrapper.Search(ctx, req) + mainRequestDuration := time.Since(start) + + // Main request should succeed quickly despite background timeout + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + assert.Less(t, mainRequestDuration, 50*time.Millisecond, "Main request should not be blocked by background timeout") + + // Wait for background context to be canceled + select { + case canceledCtx := <-unifiedClient.contextCanceled: + assert.Error(t, canceledCtx.Err(), "Background context should be canceled") + assert.Equal(t, context.DeadlineExceeded, canceledCtx.Err()) + case <-time.After(700 * time.Millisecond): + t.Fatal("Background request should have been canceled due to timeout") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) + + t.Run("background request completes successfully when within timeout", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("Search", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Configure unified client to respond within the 500ms timeout + unifiedClient.SetSearchDelay(100 * time.Millisecond) // Well within 500ms timeout + unifiedClient.On("Search", mock.Anything, req, mock.Anything).Return(&resourcepb.ResourceSearchResponse{TotalHits: 0}, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + start := time.Now() + resp, err := wrapper.Search(ctx, req) + mainRequestDuration := time.Since(start) + + // Main request should succeed quickly + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + assert.Less(t, mainRequestDuration, 50*time.Millisecond, "Main request should not be blocked") + + // Wait for successful background call + select { + case <-unifiedClient.searchCalled: + // Background call completed successfully + case <-time.After(200 * time.Millisecond): + t.Fatal("Expected successful background call") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) +} + +func TestSearchWrapper_GetStats(t *testing.T) { + gr, unifiedClient, legacyClient, features := setupTestSearchClient(t) + req := &resourcepb.ResourceStatsRequest{Namespace: "test"} + expectedResponse := &resourcepb.ResourceStatsResponse{Stats: []*resourcepb.ResourceStatsResponse_Stats{{Count: 100}}} + + t.Run("uses unified client when reading from unified", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + + dual.On("ReadFromUnified", mock.Anything, gr).Return(true, nil) + unifiedClient.On("GetStats", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, features, gr) + + resp, err := wrapper.GetStats(ctx, req) + + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + dual.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + legacyClient.AssertNotCalled(t, "GetStats") + }) + + t.Run("makes background call to unified when feature flag enabled and using legacy", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("GetStats", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Expect background call to unified client + unifiedBgResponse := &resourcepb.ResourceStatsResponse{Stats: []*resourcepb.ResourceStatsResponse_Stats{{Count: 50}}} + unifiedClient.On("GetStats", mock.Anything, req, mock.Anything).Return(unifiedBgResponse, nil) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + resp, err := wrapper.GetStats(ctx, req) + + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + + // Wait for background goroutine to complete + select { + case <-unifiedClient.statsCalled: + // Background call was made + case <-time.After(100 * time.Millisecond): + t.Fatal("Background unified client GetStats call was not made within timeout") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) + + t.Run("background GetStats request times out after 500ms", func(t *testing.T) { + ctx := testutil.NewDefaultTestContext(t) + dual := &MockDualWriter{} + featuresWithFlag := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchDualReaderEnabled) + + dual.On("ReadFromUnified", mock.Anything, gr).Return(false, nil) + legacyClient.On("GetStats", mock.Anything, req, mock.Anything).Return(expectedResponse, nil) + + // Configure unified client to take longer than the 500ms timeout + unifiedClient.SetStatsDelay(600 * time.Millisecond) // Longer than 500ms timeout + unifiedClient.On("GetStats", mock.Anything, req, mock.Anything).Return((*resourcepb.ResourceStatsResponse)(nil), context.DeadlineExceeded) + + wrapper := setupTestSearchWrapper(t, dual, unifiedClient, legacyClient, featuresWithFlag, gr) + + start := time.Now() + resp, err := wrapper.GetStats(ctx, req) + mainRequestDuration := time.Since(start) + + // Main request should succeed quickly despite background timeout + require.NoError(t, err) + assert.Equal(t, expectedResponse, resp) + assert.Less(t, mainRequestDuration, 50*time.Millisecond, "Main request should not be blocked by background timeout") + + // Wait for background context to be canceled + select { + case canceledCtx := <-unifiedClient.contextCanceled: + assert.Error(t, canceledCtx.Err(), "Background context should be canceled") + assert.Equal(t, context.DeadlineExceeded, canceledCtx.Err()) + case <-time.After(700 * time.Millisecond): + t.Fatal("Background request should have been canceled due to timeout") + } + + dual.AssertExpectations(t) + legacyClient.AssertExpectations(t) + unifiedClient.AssertExpectations(t) + }) +} diff --git a/pkg/storage/unified/resource/search_server_distributor.go b/pkg/storage/unified/resource/search_server_distributor.go index 9637e3055cb..14527f0e6b3 100644 --- a/pkg/storage/unified/resource/search_server_distributor.go +++ b/pkg/storage/unified/resource/search_server_distributor.go @@ -3,6 +3,7 @@ package resource import ( "context" "hash/fnv" + "math/rand" "time" "github.com/grafana/dskit/ring" @@ -81,9 +82,14 @@ type distributorServer struct { log log.Logger } -var ringOp = ring.NewOp([]ring.InstanceState{ring.ACTIVE}, func(s ring.InstanceState) bool { - return s != ring.ACTIVE -}) +var ( + // operation used by the distributor to select only ACTIVE instances to handle search-related requests + searchRingRead = ring.NewOp([]ring.InstanceState{ring.ACTIVE}, func(s ring.InstanceState) bool { + return s != ring.ACTIVE + }) + // operation used by the search-servers to check if they own the namespace + searchOwnerRead = ring.NewOp([]ring.InstanceState{ring.JOINING, ring.ACTIVE, ring.LEAVING}, nil) +) func (ds *distributorServer) Search(ctx context.Context, r *resourcepb.ResourceSearchRequest) (*resourcepb.ResourceSearchResponse, error) { ctx, client, err := ds.getClientToDistributeRequest(ctx, r.Options.Key.Namespace, "Search") @@ -125,16 +131,21 @@ func (ds *distributorServer) getClientToDistributeRequest(ctx context.Context, n ringHasher := fnv.New32a() _, err := ringHasher.Write([]byte(namespace)) if err != nil { + ds.log.Debug("error hashing namespace", "err", err, "namespace", namespace) return ctx, nil, err } - rs, err := ds.ring.Get(ringHasher.Sum32(), ringOp, nil, nil, nil) + rs, err := ds.ring.GetWithOptions(ringHasher.Sum32(), searchRingRead, ring.WithReplicationFactor(ds.ring.ReplicationFactor())) if err != nil { + ds.log.Debug("error getting replication set from ring", "err", err, "namespace", namespace) return ctx, nil, err } - client, err := ds.clientPool.GetClientForInstance(rs.Instances[0]) + // Randomly select an instance for primitive load balancing + inst := rs.Instances[rand.Intn(len(rs.Instances))] + client, err := ds.clientPool.GetClientForInstance(inst) if err != nil { + ds.log.Debug("error getting instance client from pool", "err", err, "namespace", namespace, "searchApiInstanceId", inst.Id) return ctx, nil, err } @@ -143,9 +154,12 @@ func (ds *distributorServer) getClientToDistributeRequest(ctx context.Context, n md = make(metadata.MD) } - ds.log.Info("distributing request to ", "methodName", methodName, "instanceId", rs.Instances[0].Id) + ds.log.Info("distributing request to ", "methodName", methodName, "instanceId", inst.Id, "namespace", namespace) - _ = grpc.SetHeader(ctx, metadata.Pairs("proxied-instance-id", rs.Instances[0].Id)) + err = grpc.SetHeader(ctx, metadata.Pairs("proxied-instance-id", inst.Id)) + if err != nil { + ds.log.Debug("error setting grpc header", err, "err") + } return userutils.InjectOrgID(metadata.NewOutgoingContext(ctx, md), namespace), client.(*RingClient).Client, nil } diff --git a/pkg/storage/unified/resource/search_test.go b/pkg/storage/unified/resource/search_test.go index 25f7f3280c0..ab8cee98432 100644 --- a/pkg/storage/unified/resource/search_test.go +++ b/pkg/storage/unified/resource/search_test.go @@ -246,7 +246,7 @@ func TestBuildIndexes_MaxCountThreshold(t *testing.T) { InitMaxCount: tt.initMaxSize, } - support, err := newSearchSupport(opts, storage, nil, nil, noop.NewTracerProvider().Tracer("test"), nil) + support, err := newSearchSupport(opts, storage, nil, nil, noop.NewTracerProvider().Tracer("test"), nil, nil, nil) require.NoError(t, err) require.NotNil(t, support) @@ -304,7 +304,7 @@ func TestSearchGetOrCreateIndex(t *testing.T) { InitMaxCount: 0, } - support, err := newSearchSupport(opts, storage, nil, nil, noop.NewTracerProvider().Tracer("test"), nil) + support, err := newSearchSupport(opts, storage, nil, nil, noop.NewTracerProvider().Tracer("test"), nil, nil, nil) require.NoError(t, err) require.NotNil(t, support) diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index b06ee3e7240..78bf79627da 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -20,6 +20,7 @@ import ( claims "github.com/grafana/authlib/types" "github.com/grafana/dskit/backoff" + "github.com/grafana/dskit/ring" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" @@ -180,6 +181,8 @@ type SearchOptions struct { // Interval for periodic index rebuilds (0 disables periodic rebuilds) RebuildInterval time.Duration + + Ring *ring.Ring } type ResourceServerOptions struct { @@ -223,6 +226,9 @@ type ResourceServerOptions struct { // QOSQueue is the quality of service queue used to enqueue QOSQueue QOSEnqueuer + + Ring *ring.Ring + RingLifecycler *ring.BasicLifecycler } func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { @@ -305,7 +311,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { if opts.Search.Resources != nil { var err error - s.search, err = newSearchSupport(opts.Search, s.backend, s.access, s.blob, opts.Tracer, opts.IndexMetrics) + s.search, err = newSearchSupport(opts.Search, s.backend, s.access, s.blob, opts.Tracer, opts.IndexMetrics, opts.Ring, opts.RingLifecycler) if err != nil { return nil, err } diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 97163958972..e89b20a0780 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/otel/trace" "github.com/grafana/authlib/types" + "github.com/grafana/dskit/ring" "github.com/grafana/dskit/services" infraDB "github.com/grafana/grafana/pkg/infra/db" @@ -37,6 +38,8 @@ type ServerOptions struct { IndexMetrics *resource.BleveIndexMetrics Features featuremgmt.FeatureToggles QOSQueue QOSEnqueueDequeuer + Ring *ring.Ring + RingLifecycler *ring.BasicLifecycler } // Creates a new ResourceServer @@ -96,6 +99,8 @@ func NewResourceServer( serverOptions.Search = opts.SearchOptions serverOptions.IndexMetrics = opts.IndexMetrics serverOptions.QOSQueue = opts.QOSQueue + serverOptions.Ring = opts.Ring + serverOptions.RingLifecycler = opts.RingLifecycler return resource.NewResourceServer(serverOptions) } diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index c8a2ef8260d..cdc6ee97ca3 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -75,8 +75,8 @@ type service struct { docBuilders resource.DocumentBuilderSupplier - storageRing *ring.Ring - lifecycler *ring.BasicLifecycler + searchRing *ring.Ring + ringLifecycler *ring.BasicLifecycler queue QOSEnqueueDequeuer scheduler *scheduler.Scheduler @@ -91,7 +91,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders resource.DocumentBuilderSupplier, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, - storageRing *ring.Ring, + searchRing *ring.Ring, memberlistKVConfig kv.Config, ) (UnifiedStorageGrpcService, error) { var err error @@ -116,7 +116,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders: docBuilders, storageMetrics: storageMetrics, indexMetrics: indexMetrics, - storageRing: storageRing, + searchRing: searchRing, subservicesWatcher: services.NewFailureWatcher(), } @@ -143,7 +143,7 @@ func ProvideUnifiedStorageGrpcService( delegate = ring.NewLeaveOnStoppingDelegate(delegate, log) delegate = ring.NewAutoForgetDelegate(resource.RingHeartbeatTimeout*2, delegate, log) - s.lifecycler, err = ring.NewBasicLifecycler( + s.ringLifecycler, err = ring.NewBasicLifecycler( lifecyclerCfg, resource.RingName, resource.RingKey, @@ -155,7 +155,9 @@ func ProvideUnifiedStorageGrpcService( if err != nil { return nil, fmt.Errorf("failed to initialize storage-ring lifecycler: %s", err) } - subservices = append(subservices, s.lifecycler) + + s.ringLifecycler.SetKeepInstanceInTheRingOnShutdown(true) + subservices = append(subservices, s.ringLifecycler) } if cfg.QOSEnabled { @@ -220,6 +222,8 @@ func (s *service) starting(ctx context.Context) error { IndexMetrics: s.indexMetrics, Features: s.features, QOSQueue: s.queue, + Ring: s.searchRing, + RingLifecycler: s.ringLifecycler, } server, err := NewResourceServer(serverOptions) if err != nil { @@ -254,12 +258,12 @@ func (s *service) starting(ctx context.Context) error { s.log.Info("waiting until resource server is JOINING in the ring") lfcCtx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - if err := ring.WaitInstanceState(lfcCtx, s.storageRing, s.lifecycler.GetInstanceID(), ring.JOINING); err != nil { + if err := ring.WaitInstanceState(lfcCtx, s.searchRing, s.ringLifecycler.GetInstanceID(), ring.JOINING); err != nil { return fmt.Errorf("error switching to JOINING in the ring: %s", err) } s.log.Info("resource server is JOINING in the ring") - if err := s.lifecycler.ChangeState(ctx, ring.ACTIVE); err != nil { + if err := s.ringLifecycler.ChangeState(ctx, ring.ACTIVE); err != nil { return fmt.Errorf("error switching to ACTIVE in the ring: %s", err) } s.log.Info("resource server is ACTIVE in the ring") diff --git a/pkg/tests/apis/alerting/notifications/timeinterval/test-data/notification-settings.json b/pkg/tests/apis/alerting/notifications/timeinterval/test-data/notification-settings.json index 80f1b0438a0..33be4d06be5 100644 --- a/pkg/tests/apis/alerting/notifications/timeinterval/test-data/notification-settings.json +++ b/pkg/tests/apis/alerting/notifications/timeinterval/test-data/notification-settings.json @@ -15,6 +15,9 @@ ], "mute_time_intervals": [ "test-interval", "persisted-interval" + ], + "active_time_intervals": [ + "test-interval", "persisted-interval" ] } ] diff --git a/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go b/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go index 8ea61ab6716..73656fcbc17 100644 --- a/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go +++ b/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go @@ -725,7 +725,9 @@ func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) { updatedRoute := legacyCli.GetRoute(t) for idx, route := range updatedRoute.Routes { expectedTimeIntervals := replace(currentRoute.Routes[idx].MuteTimeIntervals, interval.Spec.Name, actual.Spec.Name) - assert.Equalf(t, expectedTimeIntervals, route.MuteTimeIntervals, "time interval in routes should have been renamed but it did not") + assert.Equalf(t, expectedTimeIntervals, route.MuteTimeIntervals, "mute time interval in routes should have been renamed but it did not") + expectedTimeIntervals = replace(currentRoute.Routes[idx].ActiveTimeIntervals, interval.Spec.Name, actual.Spec.Name) + assert.Equalf(t, expectedTimeIntervals, route.ActiveTimeIntervals, "active time interval in routes should have been renamed but it did not") } interval = *actual diff --git a/pkg/tests/apis/dashboard/dashboards_test.go b/pkg/tests/apis/dashboard/dashboards_test.go index 900b7ed7da2..7e8c36c003a 100644 --- a/pkg/tests/apis/dashboard/dashboards_test.go +++ b/pkg/tests/apis/dashboard/dashboards_test.go @@ -23,7 +23,8 @@ import ( dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" ) func TestMain(m *testing.M) { @@ -164,10 +165,10 @@ func TestIntegrationDashboardsAppV1(t *testing.T) { } } -func TestIntegrationDashboardsAppV2(t *testing.T) { +func TestIntegrationDashboardsAppV2alpha1(t *testing.T) { gvr := schema.GroupVersionResource{ - Group: dashboardV2.GROUP, - Version: dashboardV2.VERSION, + Group: dashboardV2alpha1.GROUP, + Version: dashboardV2alpha1.VERSION, Resource: "dashboards", } if testing.Short() { @@ -176,7 +177,33 @@ func TestIntegrationDashboardsAppV2(t *testing.T) { modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} for _, mode := range modes { - t.Run(fmt.Sprintf("v1beta1 with dual writer mode %d", mode), func(t *testing.T) { + t.Run(fmt.Sprintf("v2alpha1 with dual writer mode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableAnonymous: true, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: mode, + }, + }, + }) + runDashboardTest(t, helper, gvr) + }) + } +} + +func TestIntegrationDashboardsAppV2alpha2(t *testing.T) { + gvr := schema.GroupVersionResource{ + Group: dashboardV2alpha2.GROUP, + Version: dashboardV2alpha2.VERSION, + Resource: "dashboards", + } + if testing.Short() { + t.Skip("skipping integration test") + } + + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, mode := range modes { + t.Run(fmt.Sprintf("v1alpha2 with dual writer mode %d", mode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ @@ -223,7 +250,7 @@ func TestIntegrationLegacySupport(t *testing.T) { clientV2 := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: dashboardV2.DashboardResourceInfo.GroupVersionResource(), + GVR: dashboardV2alpha1.DashboardResourceInfo.GroupVersionResource(), }) obj, err = clientV2.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/dashboard-test-v2.yaml"), diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 42e4d9d9a5f..881ea3c1430 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -16,7 +16,8 @@ import ( dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" foldersV1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -55,8 +56,8 @@ type TestContext struct { OrgID int64 } -// TestIntegrationValidation tests the dashboard K8s API -func TestIntegrationValidation(t *testing.T) { +// TestIntegrationDashboardAPIValidation tests the dashboard K8s API with validation checks +func TestIntegrationDashboardAPIValidation(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } @@ -78,7 +79,19 @@ func TestIntegrationValidation(t *testing.T) { }, }}) - testIntegrationValidationForServer(t, helper, dualWriterMode) + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + + t.Run("Dashboard validation tests", func(t *testing.T) { + runDashboardValidationTests(t, org1Ctx) + }) + + t.Run("Dashboard quota tests", func(t *testing.T) { + runQuotaTests(t, org1Ctx) + }) }) } @@ -113,45 +126,110 @@ func TestIntegrationValidation(t *testing.T) { } } -func testIntegrationValidationForServer(t *testing.T, helper *apis.K8sTestHelper, dualWriterMode rest.DualWriterMode) { - t.Cleanup(func() { - helper.Shutdown() - }) +// TestIntegrationDashboardAPIAuthorization tests the dashboard K8s API with authorization checks +func TestIntegrationDashboardAPIAuthorization(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } - // Create test contexts organization - org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) - org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, dualWriterMode := range dualWriterModes { + t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableAnonymous: true, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature + featuremgmt.FlagUnifiedStorageSearch, + }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: dualWriterMode, + }, + "folders.folder.grafana.app": { + DualWriterMode: dualWriterMode, + }, + }}) - t.Run("Organization 1 tests", func(t *testing.T) { - t.Run("Dashboard validation tests", func(t *testing.T) { - runDashboardValidationTests(t, org1Ctx) + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx, false) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) + }) + + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) }) + } +} - t.Run("Dashboard quota tests", func(t *testing.T) { - runQuotaTests(t, org1Ctx) +// TestIntegrationDashboardAPI tests the dashboard K8s API +func TestIntegrationDashboardAPI(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, dualWriterMode := range dualWriterModes { + t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { + // Create a K8sTestHelper which will set up a real API server + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableAnonymous: true, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature + featuremgmt.FlagUnifiedStorageSearch, + featuremgmt.FlagKubernetesDashboards, + }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: dualWriterMode, + }, + "folders.folder.grafana.app": { + DualWriterMode: dualWriterMode, + }, + }}) + + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + + t.Run("Dashboard LIST API test", func(t *testing.T) { + runDashboardListTests(t, org1Ctx) + }) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx, true) + }) + + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) + }) }) - - t.Run("Authorization tests for all identity types", func(t *testing.T) { - runAuthorizationTests(t, org1Ctx) - }) - - t.Run("Dashboard permission tests", func(t *testing.T) { - runDashboardPermissionTests(t, org1Ctx, true) - }) - - t.Run("Dashboard LIST API test", func(t *testing.T) { - t.Skip("Skip LIST") - runDashboardListTest(t, org1Ctx) - }) - }) - - t.Run("Dashboard HTTP API test", func(t *testing.T) { - runDashboardHttpTest(t, org1Ctx, org2Ctx) - }) - - t.Run("Cross-organization tests", func(t *testing.T) { - runCrossOrgTests(t, org1Ctx, org2Ctx) - }) + } } // Auth identity types (user or token) with resource client @@ -168,7 +246,11 @@ type Identity struct { func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Helper() - adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + // Get a new resource client for admin user + // TODO: we need to figure out why reusing the same client results in slower tests + adminClient := func() *apis.K8sResourceClient { + return getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + } editorClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) t.Run("Dashboard UID validations", func(t *testing.T) { @@ -176,15 +258,15 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with existing UID", func(t *testing.T) { // Create a dashboard with a specific UID specificUID := "existing-uid-dash" - createdDash, err := createDashboard(t, adminClient, "Dashboard with Specific UID", nil, &specificUID) + createdDash, err := createDashboard(t, adminClient(), "Dashboard with Specific UID", nil, &specificUID) require.NoError(t, err) // Try to create another dashboard with the same UID - _, err = createDashboard(t, adminClient, "Another Dashboard with Same UID", nil, &specificUID) + _, err = createDashboard(t, adminClient(), "Another Dashboard with Same UID", nil, &specificUID) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), createdDash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), createdDash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) @@ -192,14 +274,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with too long UID", func(t *testing.T) { // Create a dashboard with a long UID (over 40 chars) longUID := "this-uid-is-way-too-long-for-a-dashboard-uid-12345678901234567890" - _, err := createDashboard(t, adminClient, "Dashboard with Long UID", nil, &longUID) + _, err := createDashboard(t, adminClient(), "Dashboard with Long UID", nil, &longUID) require.Error(t, err) }) // Test creating dashboard with invalid UID characters t.Run("reject dashboard with invalid UID characters", func(t *testing.T) { invalidUID := "invalid/uid/with/slashes" - _, err := createDashboard(t, adminClient, "Dashboard with Invalid UID", nil, &invalidUID) + _, err := createDashboard(t, adminClient(), "Dashboard with Invalid UID", nil, &invalidUID) require.Error(t, err) }) }) @@ -208,47 +290,47 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard title validations", func(t *testing.T) { // Test empty title t.Run("reject dashboard with empty title", func(t *testing.T) { - _, err := createDashboard(t, adminClient, "", nil, nil) + _, err := createDashboard(t, adminClient(), "", nil, nil) require.Error(t, err) }) // Test long title t.Run("reject dashboard with excessively long title", func(t *testing.T) { veryLongTitle := strings.Repeat("a", 10000) - _, err := createDashboard(t, adminClient, veryLongTitle, nil, nil) + _, err := createDashboard(t, adminClient(), veryLongTitle, nil, nil) require.Error(t, err) }) // Test updating dashboard with empty title t.Run("reject dashboard update with empty title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient(), "Valid Dashboard Title", nil, nil) require.NoError(t, err) require.NotNil(t, dash) // Try to update with empty title - _, err = updateDashboard(t, adminClient, dash, "", nil) + _, err = updateDashboard(t, adminClient(), dash, "", nil) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) // Test updating dashboard with excessively long title t.Run("reject dashboard update with excessively long title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient(), "Valid Dashboard Title", nil, nil) require.NoError(t, err) require.NotNil(t, dash) // Try to update with excessively long title veryLongTitle := strings.Repeat("a", 10000) - _, err = updateDashboard(t, adminClient, dash, veryLongTitle, nil) + _, err = updateDashboard(t, adminClient(), dash, veryLongTitle, nil) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -256,15 +338,15 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard message validations", func(t *testing.T) { // Test long message t.Run("reject dashboard with excessively long update message", func(t *testing.T) { - dash, err := createDashboard(t, adminClient, "Regular dashboard", nil, nil) + dash, err := createDashboard(t, adminClient(), "Regular dashboard", nil, nil) require.NoError(t, err) veryLongMessage := strings.Repeat("a", 600) - _, err = updateDashboard(t, adminClient, dash, "Dashboard updated with a long message", &veryLongMessage) + _, err = updateDashboard(t, adminClient(), dash, "Dashboard updated with a long message", &veryLongMessage) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -273,21 +355,21 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test non-existent folder UID t.Run("reject dashboard with non-existent folder UID", func(t *testing.T) { nonExistentFolderUID := "non-existent-folder-uid" - _, err := createDashboard(t, adminClient, "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) + _, err := createDashboard(t, adminClient(), "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) ctx.Helper.EnsureStatusError(err, http.StatusNotFound, "folders.folder.grafana.app \"non-existent-folder-uid\" not found") }) t.Run("allow moving folder to general folder", func(t *testing.T) { folder1 := createFolderObject(t, "folder1", "default", "") folder1UID := folder1.GetName() - dash, err := createDashboard(t, adminClient, "Dashboard in a Folder", &folder1UID, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard in a Folder", &folder1UID, nil) require.NoError(t, err) generalFolderUID := "" - _, err = updateDashboard(t, adminClient, dash, "Move dashboard into the General Folder", &generalFolderUID) + _, err = updateDashboard(t, adminClient(), dash, "Move dashboard into the General Folder", &generalFolderUID) require.NoError(t, err) - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -345,11 +427,29 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { }, { name: "v2alpha1 dashboard with correct spec should not throw on v2", - resourceInfo: dashboardV2.DashboardResourceInfo, + resourceInfo: dashboardV2alpha1.DashboardResourceInfo, expectSpecErr: false, testObject: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": dashboardV2.DashboardResourceInfo.TypeMeta().APIVersion, + "apiVersion": dashboardV2alpha1.DashboardResourceInfo.TypeMeta().APIVersion, + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "generateName": "test-", + }, + "spec": map[string]interface{}{ + "title": "Dashboard Title", + "description": "valid description", + }, + }, + }, + }, + { + name: "v2alpha2 dashboard with correct spec should not throw on v2", + resourceInfo: dashboardV2alpha2.DashboardResourceInfo, + expectSpecErr: false, + testObject: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": dashboardV2alpha2.DashboardResourceInfo.TypeMeta().APIVersion, "kind": "Dashboard", "metadata": map[string]interface{}{ "generateName": "test-", @@ -384,7 +484,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test version increment on update t.Run("version increments on dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Version Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for version test") dashUID := dash.GetName() @@ -394,7 +494,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { initialRV := meta.GetResourceVersion() // Update the dashboard - updatedDash, err := updateDashboard(t, adminClient, dash, "Updated Dashboard for Version Test", nil) + updatedDash, err := updateDashboard(t, adminClient(), dash, "Updated Dashboard for Version Test", nil) require.NoError(t, err) require.NotNil(t, updatedDash) @@ -404,25 +504,25 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.NotEqual(t, meta.GetResourceVersion(), initialRV, "Resource version should be changed after update") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) // Test generation conflict when updating concurrently t.Run("reject update with version conflict", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Conflict Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Version Conflict Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for version conflict test") dashUID := dash.GetName() // Get the dashboard twice (simulating two users getting it) - dash1, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + dash1, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) dash2, err := editorClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) // Update with the first copy - updatedDash1, err := updateDashboard(t, adminClient, dash1, "Updated by first user", nil) + updatedDash1, err := updateDashboard(t, adminClient(), dash1, "Updated by first user", nil) require.NoError(t, err) require.NotNil(t, updatedDash1) @@ -432,7 +532,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.Contains(t, err.Error(), "the object has been modified", "Should fail with version conflict error") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) @@ -445,12 +545,12 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { meta.SetGeneration(5) // Create the dashboard - createdDash, err := adminClient.Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) + createdDash, err := adminClient().Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) require.NoError(t, err) dashUID := createdDash.GetName() // Fetch the created dashboard - fetchedDash, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + fetchedDash, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) // Verify the generation was handled properly @@ -458,22 +558,22 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.Equal(t, 5, meta.GetGeneration(), "Generation should be 5") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) t.Run("dashboard version history available, even for UIDs ending in hyphen", func(t *testing.T) { dashboardUID := "test-dashboard-" - dash, err := createDashboard(t, adminClient, "Dashboard with uid ending in hyphen", nil, &dashboardUID) + dash, err := createDashboard(t, adminClient(), "Dashboard with uid ending in hyphen", nil, &dashboardUID) require.NoError(t, err) - updatedDash, err := updateDashboard(t, adminClient, dash, "Updated dashboard with uid ending in hyphen", nil) + updatedDash, err := updateDashboard(t, adminClient(), dash, "Updated dashboard with uid ending in hyphen", nil) require.NoError(t, err) require.NotNil(t, updatedDash) labelSelector := utils.LabelKeyGetHistory + "=true" fieldSelector := "metadata.name=" + dashboardUID - versions, err := adminClient.Resource.List(context.Background(), v1.ListOptions{ + versions, err := adminClient().Resource.List(context.Background(), v1.ListOptions{ LabelSelector: labelSelector, FieldSelector: fieldSelector, Limit: 10, @@ -483,7 +583,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // one from initial save, one from update require.Equal(t, len(versions.Items), 2) - err = adminClient.Resource.Delete(context.Background(), dashboardUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashboardUID, v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -511,12 +611,12 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Provisioning Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Provisioning Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for provisioning test") dashUID := dash.GetName() // Fetch the created dashboard - fetchedDash, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + fetchedDash, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) require.NotNil(t, fetchedDash) @@ -524,7 +624,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { provisionedDash := markDashboardObjectAsProvisioned(t, fetchedDash, "test-provider", "test-external-id", "test-checksum", tc.allowsEdits) // Update the dashboard to apply the provisioning annotations - updatedDash, err := adminClient.Resource.Update(context.Background(), provisionedDash, v1.UpdateOptions{}) + updatedDash, err := adminClient().Resource.Update(context.Background(), provisionedDash, v1.UpdateOptions{}) require.NoError(t, err) require.NotNil(t, updatedDash) @@ -551,16 +651,13 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) } }) t.Run("Dashboard refresh interval validations", func(t *testing.T) { - // Create test client - adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) - // Store original settings to restore after test origCfg := ctx.Helper.GetEnv().Cfg origMinRefreshInterval := origCfg.MinRefreshInterval @@ -620,14 +717,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { _ = meta.SetSpec(specMap) - dash, err := adminClient.Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) + dash, err := adminClient().Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) if tc.shouldSucceed { require.NoError(t, err) require.NotNil(t, dash) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } else { require.Error(t, err) @@ -645,7 +742,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Create a dashboard with a specific UID to make it easier to manage specificUID := "size-limit-test-dash" - dash, err := createDashboard(t, adminClient, "Dashboard Exceeding Size Limit", nil, &specificUID) + dash, err := createDashboard(t, adminClient(), "Dashboard Exceeding Size Limit", nil, &specificUID) require.NoError(t, err) meta, _ := utils.MetaAccessor(dash) @@ -685,44 +782,17 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.NoError(t, err, "Failed to set spec") // Try to update with too many panels - _, err = adminClient.Resource.Update(context.Background(), dash, v1.UpdateOptions{}) + _, err = adminClient().Resource.Update(context.Background(), dash, v1.UpdateOptions{}) require.Error(t, err) require.Contains(t, err.Error(), "exceeds", "Error should mention size or limit exceeded") // Clean up - err = adminClient.Resource.Delete(context.Background(), specificUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), specificUID, v1.DeleteOptions{}) require.NoError(t, err) }) }) } -// skipIfMode skips the current test if running in any of the specified modes -// Usage: skipIfMode(t, rest.Mode1, rest.Mode4) -// or with a message: skipIfMode(t, "Known issue with conflict detection", rest.Mode1, rest.Mode4) -// nolint:unused -func (c *TestContext) skipIfMode(t *testing.T, args ...interface{}) { - t.Helper() - - message := "Test not supported in this dual writer mode" - modes := []rest.DualWriterMode{} - - // Parse args - first string is considered a message, all rest.DualWriterMode values are modes to skip - for _, arg := range args { - if msg, ok := arg.(string); ok { - message = msg - } else if mode, ok := arg.(rest.DualWriterMode); ok { - modes = append(modes, mode) - } - } - - // Check if current mode is in the list of modes to skip - for _, mode := range modes { - if c.DualWriterMode == mode { - t.Skipf("%s (mode %d)", message, c.DualWriterMode) - } - } -} - // Run tests for quota validation func runQuotaTests(t *testing.T, ctx TestContext) { t.Helper() @@ -1071,6 +1141,13 @@ func updateDashboard(t *testing.T, client *apis.K8sResourceClient, dashboard *un func runAuthorizationTests(t *testing.T, ctx TestContext) { t.Helper() + // Get a new resource client for admin user + // TODO: we need to figure out why reusing the same client results in slower tests + adminClient := func() *apis.K8sResourceClient { + // admin token + return getServiceAccountResourceClient(t, ctx.Helper, ctx.AdminServiceAccountToken, ctx.OrgID, getDashboardGVR()) + } + // Get clients for each identity type and role adminUserClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) editorUserClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) @@ -1093,11 +1170,13 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { {Name: "Viewer token", DashboardClient: viewerTokenClient, Type: "token"}, } + // TODO: re-enable admin cleanup clients when we have figured out why reusing the same client results in slower tests + // TODO: This is currently disabled to avoid issues with reusing the same client in tests. // Get admin clients for cleanup based on identity type - adminCleanupClients := map[string]*apis.K8sResourceClient{ - "user": adminUserClient, - "token": adminTokenClient, - } + // adminCleanupClients := map[string]*apis.K8sResourceClient{ + // "user": adminUserClient, + // "token": adminTokenClient, + // } // Define test cases for different roles type roleTest struct { @@ -1143,8 +1222,9 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { for _, identity := range identities { identity := identity // Capture range variable t.Run(identity.Name, func(t *testing.T) { + // TODO: This is currently disabled to avoid issues with reusing the same client in tests. // Get admin client for cleanup based on identity type - adminClient := adminCleanupClients[identity.Type] + // adminClient := adminCleanupClients[identity.Type] // Get role capabilities for this identity roleCapabilities := authTests[identity.DashboardClient] @@ -1176,7 +1256,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } else { // Test cannot create dashboard @@ -1190,7 +1270,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard updates t.Run("dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard to Update by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard to Update by "+identity.Name, nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1210,14 +1290,14 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) // Test dashboard deletion permissions t.Run("dashboard deletion", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for deletion test by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for deletion test by "+identity.Name, nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1228,7 +1308,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } else { require.Error(t, err, "Should not be able to delete dashboard") // Clean up with admin if the test identity couldn't delete - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } }) @@ -1237,7 +1317,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard viewing for all roles t.Run("dashboard viewing", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for "+identity.Name+" to view", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for "+identity.Name+" to view", nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1247,7 +1327,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { require.NotNil(t, viewedDash) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -2078,6 +2158,8 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte // Helper function to retrieve a dashboard via HTTP func getDashboardViaHTTP(t *testing.T, ctx *TestContext, dashboardPath string, user apis.User) (map[string]interface{}, error) { + t.Helper() + getResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ User: user, Method: http.MethodGet, @@ -2175,7 +2257,7 @@ func testDashboardHttpUpdateMethods(t *testing.T, ctx TestContext, dashboardPath } // Test dashboard list API with complex permission scenarios -func runDashboardListTest(t *testing.T, ctx TestContext) { +func runDashboardListTests(t *testing.T, ctx TestContext) { t.Helper() // Make sure no dashboards exist before we start @@ -2204,8 +2286,8 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { // Define a map of user types to their clients clients := map[string]struct { userClient *apis.K8sResourceClient - tokenClient *apis.K8sResourceClient folderClient *apis.K8sResourceClient + tokenClient *apis.K8sResourceClient }{ "Admin": { userClient: getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()), @@ -2304,6 +2386,24 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { folders := make([]*folder.Folder, len(folderConfigs)) folderDashboards := make([]*unstructured.Unstructured, len(folderConfigs)) + // Clean up + t.Cleanup(func() { + // Delete all root dashboards + for _, dash := range rootDashboards { + err := adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + } + + // Delete all folder dashboards and folders + for i, folder := range folders { + err := adminClient.Resource.Delete(context.Background(), folderDashboards[i].GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + + err = adminFolderClient.Resource.Delete(context.Background(), folder.UID, v1.DeleteOptions{}) + require.NoError(t, err) + } + }) + // Create all test resources (folders, dashboards) in one loop for i, fc := range folderConfigs { // Create root dashboard @@ -2396,71 +2496,55 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { // Test LIST operation for each identity for _, identity := range identities { - t.Run(fmt.Sprintf("LIST operation for %s", identity.Name), func(t *testing.T) { - // Get dashboards visible to this identity - clients := []apis.K8sResourceClient{ - *identity.DashboardClient, - *identity.FolderClient, - } + // Get dashboards visible to this identity + clients := []apis.K8sResourceClient{ + *identity.DashboardClient, + *identity.FolderClient, + } + for _, client := range clients { + t.Run(fmt.Sprintf("LIST operation for %s and %s", identity.Name, client.Args.GVR), func(t *testing.T) { + // Use the client to list all resources + dashList, err := client.Resource.List(context.Background(), v1.ListOptions{}) + require.NoError(t, err) - for _, client := range clients { - t.Run(fmt.Sprintf("LIST operation for %s", client.Args.GVR), func(t *testing.T) { - listOpts := v1.ListOptions{} - dashList, err := client.Resource.List(context.Background(), listOpts) + if len(dashList.Items) == 0 { + t.Logf("WARNING: Got empty dashboard list for %s", identity.Name) + } + + require.NotEmpty(t, dashList.Items) + + // Extract dashboard titles + dashTitles := make([]string, 0, len(dashList.Items)) + for _, dash := range dashList.Items { + meta, err := utils.MetaAccessor(&dash) require.NoError(t, err) - require.NotEmpty(t, dashList.Items) - // Extract dashboard titles - dashTitles := make([]string, 0, len(dashList.Items)) - for _, dash := range dashList.Items { - meta, err := utils.MetaAccessor(&dash) - require.NoError(t, err) + dashTitles = append(dashTitles, meta.FindTitle("")) + } - dashTitles = append(dashTitles, meta.FindTitle("")) - } + // Verify expectations + var expectedTitles []string + if client.Args.GVR == getDashboardGVR() { + expectedTitles = expectations[identity.Name] + } else { + expectedTitles = folderPermissions[identity.Name] + } + require.ElementsMatch(t, expectedTitles, dashTitles) - // Verify expectations - var expectedTitles []string - if client.Args.GVR == getDashboardGVR() { - expectedTitles = expectations[identity.Name] - } else { - expectedTitles = folderPermissions[identity.Name] - } - require.ElementsMatch(t, expectedTitles, dashTitles) - - // Verify all expected items are found - for _, expected := range expectedTitles { - found := false - for _, title := range dashTitles { - if title == expected { - found = true - break - } + // Verify all expected items are found + for _, expected := range expectedTitles { + found := false + for _, title := range dashTitles { + if title == expected { + found = true + break } - require.True(t, found, "%s should see dashboard '%s' but didn't", identity.Name, expected) } - }) - } - }) + require.True(t, found, "%s should see dashboard '%s' but didn't", identity.Name, expected) + } + }) + } } - - // Clean up - t.Run("Cleanup dashboards and folders", func(t *testing.T) { - // Delete all root dashboards - for _, dash := range rootDashboards { - err := adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) - require.NoError(t, err) - } - - // Delete all folder dashboards and folders - for i, folder := range folders { - err := adminClient.Resource.Delete(context.Background(), folderDashboards[i].GetName(), v1.DeleteOptions{}) - require.NoError(t, err) - - err = adminFolderClient.Resource.Delete(context.Background(), folder.UID, v1.DeleteOptions{}) - require.NoError(t, err) - } - }) } func postHelper(t *testing.T, ctx *TestContext, path string, body interface{}, user apis.User) (map[string]interface{}, error) { diff --git a/pkg/tests/apis/iam/iam_test.go b/pkg/tests/apis/iam/iam_test.go index 65af0d8e75f..2deaf6a94fd 100644 --- a/pkg/tests/apis/iam/iam_test.go +++ b/pkg/tests/apis/iam/iam_test.go @@ -198,6 +198,7 @@ func TestIntegrationUsers(t *testing.T) { }, EnableFeatureToggles: []string{ featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, }, }) doUserCRUDTestsUsingTheNewAPIs(t, helper) diff --git a/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json index a5fe373b77a..c4913b76d5d 100644 --- a/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json @@ -864,17 +864,302 @@ } } ] + }, + "/apis/playlist.grafana.app/v0alpha1/namespaces/{namespace}/playlists/{name}/status": { + "get": { + "tags": [ + "Playlist" + ], + "description": "read status of the specified Playlist", + "operationId": "getPlaylistStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "playlist.grafana.app", + "version": "v0alpha1", + "kind": "Playlist" + } + }, + "put": { + "tags": [ + "Playlist" + ], + "description": "replace status of the specified Playlist", + "operationId": "replacePlaylistStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "playlist.grafana.app", + "version": "v0alpha1", + "kind": "Playlist" + } + }, + "patch": { + "tags": [ + "Playlist" + ], + "description": "partially update status of the specified Playlist", + "operationId": "updatePlaylistStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "playlist.grafana.app", + "version": "v0alpha1", + "kind": "Playlist" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Playlist", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] } }, "components": { "schemas": { "com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.Playlist": { "type": "object", - "required": [ - "metadata", - "spec", - "status" - ], "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", @@ -893,7 +1178,6 @@ ] }, "spec": { - "description": "Spec is the spec of the Playlist", "default": {}, "allOf": [ { @@ -918,25 +1202,6 @@ } ] }, - "com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.PlaylistItem": { - "type": "object", - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "description": "type of the item.", - "type": "string", - "default": "" - }, - "value": { - "description": "Value depends on type and describes the playlist item.\n - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This\n is not portable as the numerical identifier is non-deterministic between different instances.\n Will be replaced by dashboard_by_uid in the future. (deprecated)\n - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All\n dashboards behind the tag will be added to the playlist.\n - dashboard_by_uid: The value is the dashboard UID", - "type": "string", - "default": "" - } - } - }, "com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.PlaylistList": { "type": "object", "required": [ @@ -989,23 +1254,35 @@ ], "properties": { "interval": { - "type": "string", - "default": "" + "type": "string" }, "items": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.PlaylistItem" + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "description": "type of the item.", + "type": "string", + "enum": [ + "dashboard_by_tag", + "dashboard_by_uid", + "dashboard_by_id" + ] + }, + "value": { + "description": "Value depends on type and describes the playlist item.\n - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This\n is not portable as the numerical identifier is non-deterministic between different instances.\n Will be replaced by dashboard_by_uid in the future. (deprecated)\n - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All\n dashboards behind the tag will be added to the playlist.\n - dashboard_by_uid: The value is the dashboard UID", + "type": "string" } - ] + } } }, "title": { - "type": "string", - "default": "" + "type": "string" } } }, @@ -1015,54 +1292,45 @@ "additionalFields": { "description": "additionalFields is reserved for future use", "type": "object", - "additionalProperties": { - "type": "object" - } + "additionalProperties": true }, "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", "type": "object", "additionalProperties": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.PlayliststatusOperatorState" + "type": "object", + "required": [ + "lastEvaluation", + "state" + ], + "properties": { + "descriptiveState": { + "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", + "type": "string" + }, + "details": { + "description": "details contains any extra information that is operator-specific", + "type": "object", + "additionalProperties": true + }, + "lastEvaluation": { + "description": "lastEvaluation is the ResourceVersion last evaluated", + "type": "string" + }, + "state": { + "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", + "type": "string", + "enum": [ + "success", + "in_progress", + "failed" + ] } - ] + } } } } }, - "com.github.grafana.grafana.apps.playlist.pkg.apis.playlist.v0alpha1.PlayliststatusOperatorState": { - "type": "object", - "required": [ - "lastEvaluation", - "state" - ], - "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string", - "default": "" - }, - "state": { - "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - "type": "string", - "default": "" - } - } - }, "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { "description": "APIResource specifies the name of a resource and whether it is namespaced.", "type": "object", diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index 3fc3d7c5bb4..bd54e67e82a 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -1281,6 +1281,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "originalPath", + "in": "query", + "description": "path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -\u003e 'c/d.json') or folder-to-folder (e.g., 'some/' -\u003e 'new/')", + "schema": { + "type": "string" + } } ], "requestBody": { @@ -1385,6 +1393,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "originalPath", + "in": "query", + "description": "path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -\u003e 'c/d.json') or folder-to-folder (e.g., 'some/' -\u003e 'new/')", + "schema": { + "type": "string" + } } ], "requestBody": { @@ -1489,6 +1505,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "originalPath", + "in": "query", + "description": "path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -\u003e 'c/d.json') or folder-to-folder (e.g., 'some/' -\u003e 'new/')", + "schema": { + "type": "string" + } } ], "responses": { @@ -2607,6 +2631,23 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.DeleteJobOptions": { + "type": "object", + "properties": { + "paths": { + "description": "Paths to be deleted. Examples: - dashboard.json (for a file) - a/b/c/other-dashboard.json (for a file) - nested/deep/ (for a directory) FIXME: we should validate this in admission hooks", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "ref": { + "description": "Ref to the branch or commit hash to delete from", + "type": "string" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ErrorDetails": { "type": "object", "required": [ @@ -2629,15 +2670,19 @@ "type": "object", "properties": { "branch": { - "description": "Target branch for export (only git)", + "description": "FIXME: we should validate this in admission hooks Target branch for export (only git)", "type": "string" }, "folder": { "description": "The source folder (or empty) to export", "type": "string" }, + "message": { + "description": "Message to use when committing the changes in a single commit", + "type": "string" + }, "path": { - "description": "Prefix in target file system", + "description": "FIXME: we should validate this in admission hooks Prefix in target file system", "type": "string" } } @@ -3021,15 +3066,24 @@ "type": "object", "properties": { "action": { - "description": "Possible enum values:\n - `\"migrate\"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history.\n - `\"pr\"` adds additional useful information to a PR, such as comments with preview links and rendered images.\n - `\"pull\"` replicates the remote branch in the local copy of the repository.\n - `\"push\"` replicates the local copy of the repository in the remote branch.", + "description": "Possible enum values:\n - `\"delete\"` deletes files in the remote repository\n - `\"migrate\"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history.\n - `\"pr\"` adds additional useful information to a PR, such as comments with preview links and rendered images.\n - `\"pull\"` replicates the remote branch in the local copy of the repository.\n - `\"push\"` replicates the local copy of the repository in the remote branch.", "type": "string", "enum": [ + "delete", "migrate", "pr", "pull", "push" ] }, + "delete": { + "description": "Delete when the action is `delete`", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.DeleteJobOptions" + } + ] + }, "migrate": { "description": "Required when the action is `migrate`", "allOf": [ @@ -3096,12 +3150,13 @@ "format": "int64" }, "state": { - "description": "Possible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"working\"` The job is running", + "description": "Possible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running", "type": "string", "enum": [ "error", "pending", "success", + "warning", "working" ] }, @@ -3155,6 +3210,10 @@ "history": { "description": "Preserve history (if possible)", "type": "boolean" + }, + "message": { + "description": "Message to use when committing the changes in a single commit", + "type": "string" } } }, @@ -3705,11 +3764,12 @@ ], "properties": { "action": { - "description": "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"update\"`", + "description": "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"move\"`\n - `\"update\"`", "type": "string", "enum": [ "create", "delete", + "move", "update" ] }, @@ -4048,13 +4108,14 @@ "format": "int64" }, "state": { - "description": "pending, running, success, error\n\nPossible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"working\"` The job is running", + "description": "pending, running, success, error\n\nPossible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running", "type": "string", "default": "", "enum": [ "error", "pending", "success", + "warning", "working" ] } diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index 6d06e9c306d..cff93d553a0 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -67,6 +67,21 @@ func TestIntegrationPlaylist(t *testing.T) { }, "scope": "Namespaced", "singularResource": "playlist", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "Playlist", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], "verbs": [ "create", "delete", @@ -468,6 +483,7 @@ func doPlaylistTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelp Path: "/api/playlists", Body: []byte(legacyPayload), }, &playlist.Playlist{}) + require.Equal(t, 200, legacyCreate.Response.StatusCode) require.NotNil(t, legacyCreate.Result) uid := legacyCreate.Result.UID require.NotEmpty(t, uid) diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha1.yaml similarity index 66% rename from pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml rename to pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha1.yaml index 540b9dcc919..ddfa58720bf 100644 --- a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha1.yaml @@ -1,9 +1,9 @@ apiVersion: dashboard.grafana.app/v2alpha1 kind: Dashboard metadata: - name: test-v2 + name: test-v2alpha1 spec: - title: Test dashboard. Created at v2 + title: Test dashboard. Created at v2alpha1 layout: kind: GridLayout spec: diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha2.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha2.yaml new file mode 100644 index 00000000000..7a4079cee41 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2alpha2.yaml @@ -0,0 +1,10 @@ +apiVersion: dashboard.grafana.app/v2alpha2 +kind: Dashboard +metadata: + name: test-v2alpha2 +spec: + title: Test dashboard. Created at v2alpha2 + layout: + kind: GridLayout + spec: + items: [] diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index bd398addb04..cff6dbeeabf 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -3,6 +3,9 @@ package provisioning import ( "context" "encoding/json" + "fmt" + "net/http" + "net/url" "os" "path" "strings" @@ -21,7 +24,8 @@ import ( dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardsV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + dashboardsV2alpha2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha2" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -41,15 +45,16 @@ type provisioningTestHelper struct { *apis.K8sTestHelper ProvisioningPath string - Repositories *apis.K8sResourceClient - Jobs *apis.K8sResourceClient - Folders *apis.K8sResourceClient - DashboardsV0 *apis.K8sResourceClient - DashboardsV1 *apis.K8sResourceClient - DashboardsV2 *apis.K8sResourceClient - AdminREST *rest.RESTClient - EditorREST *rest.RESTClient - ViewerREST *rest.RESTClient + Repositories *apis.K8sResourceClient + Jobs *apis.K8sResourceClient + Folders *apis.K8sResourceClient + DashboardsV0 *apis.K8sResourceClient + DashboardsV1 *apis.K8sResourceClient + DashboardsV2alpha1 *apis.K8sResourceClient + DashboardsV2alpha2 *apis.K8sResourceClient + AdminREST *rest.RESTClient + EditorREST *rest.RESTClient + ViewerREST *rest.RESTClient } func (h *provisioningTestHelper) SyncAndWait(t *testing.T, repo string, options *provisioning.SyncJobOptions) { @@ -182,8 +187,12 @@ func (h *provisioningTestHelper) RenderObject(t *testing.T, filePath string, val // CopyToProvisioningPath copies a file to the provisioning path. // The from path is relative to test file's directory. func (h *provisioningTestHelper) CopyToProvisioningPath(t *testing.T, from, to string) { + fullPath := path.Join(h.ProvisioningPath, to) + err := os.MkdirAll(path.Dir(fullPath), 0750) + require.NoError(t, err, "failed to create directories for provisioning path") + file := h.LoadFile(from) - err := os.WriteFile(path.Join(h.ProvisioningPath, to), file, 0600) + err = os.WriteFile(fullPath, file, 0600) require.NoError(t, err, "failed to write file to provisioning path") } @@ -255,10 +264,15 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper Namespace: "default", // actually org1 GVR: dashboardV1.DashboardResourceInfo.GroupVersionResource(), }) - dashboardsV2 := helper.GetResourceClient(apis.ResourceClientArgs{ + dashboardsV2alpha1 := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, Namespace: "default", // actually org1 - GVR: dashboardV2.DashboardResourceInfo.GroupVersionResource(), + GVR: dashboardsV2alpha1.DashboardResourceInfo.GroupVersionResource(), + }) + dashboardsV2alpha2 := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: dashboardsV2alpha2.DashboardResourceInfo.GroupVersionResource(), }) // Repo client, but less guard rails. Useful for subresources. We'll need this later... @@ -289,15 +303,16 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper ProvisioningPath: provisioningPath, K8sTestHelper: helper, - Repositories: repositories, - AdminREST: adminClient, - EditorREST: editorClient, - ViewerREST: viewerClient, - Jobs: jobs, - Folders: folders, - DashboardsV0: dashboardsV0, - DashboardsV1: dashboardsV1, - DashboardsV2: dashboardsV2, + Repositories: repositories, + AdminREST: adminClient, + EditorREST: editorClient, + ViewerREST: viewerClient, + Jobs: jobs, + Folders: folders, + DashboardsV0: dashboardsV0, + DashboardsV1: dashboardsV1, + DashboardsV2alpha1: dashboardsV2alpha1, + DashboardsV2alpha2: dashboardsV2alpha2, } } @@ -324,3 +339,44 @@ func unstructuredToRepository(t *testing.T, obj *unstructured.Unstructured) *pro return repo } + +// postFilesRequest performs a direct HTTP POST request to the files API. +// This bypasses Kubernetes REST client limitations with '/' characters in subresource names. +type filesPostOptions struct { + targetPath string // The target file/directory path + originalPath string // Source path for move operations (optional) + message string // Commit message (optional) + body string // Request body content (optional) + ref string // Git ref/branch (optional) +} + +func (h *provisioningTestHelper) postFilesRequest(t *testing.T, repo string, opts filesPostOptions) *http.Response { + addr := h.GetEnv().Server.HTTPServer.Listener.Addr().String() + baseUrl := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/%s", + addr, repo, opts.targetPath) + + // Build the URL with proper query parameter encoding + parsedUrl, err := url.Parse(baseUrl) + require.NoError(t, err) + params := parsedUrl.Query() + + if opts.originalPath != "" { + params.Set("originalPath", opts.originalPath) + } + if opts.message != "" { + params.Set("message", opts.message) + } + if opts.ref != "" { + params.Set("ref", opts.ref) + } + parsedUrl.RawQuery = params.Encode() + + req, err := http.NewRequest(http.MethodPost, parsedUrl.String(), strings.NewReader(opts.body)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + return resp +} diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index 4040a854b7a..15049df999d 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "io" + "io/fs" "net/http" "os" "path/filepath" @@ -21,11 +23,50 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/extensions" - "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/tests/apis" ) +// printFileTree prints the directory structure as a tree for debugging purposes +func printFileTree(t *testing.T, rootPath string) { + t.Helper() + t.Logf("File tree for %s:", rootPath) + + err := filepath.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(rootPath, path) + if err != nil { + return err + } + + if relPath == "." { + return nil + } + + depth := strings.Count(relPath, string(filepath.Separator)) + indent := strings.Repeat(" ", depth) + + if d.IsDir() { + t.Logf("%s├── %s/", indent, d.Name()) + } else { + info, err := d.Info() + if err != nil { + t.Logf("%s├── %s (error reading info)", indent, d.Name()) + } else { + t.Logf("%s├── %s (%d bytes)", indent, d.Name(), info.Size()) + } + } + + return nil + }) + if err != nil { + t.Logf("Error walking directory: %v", err) + } +} + func TestIntegrationProvisioning_CreatingAndGetting(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") @@ -535,38 +576,42 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T helper := runGrafana(t) ctx := context.Background() + // The dashboard shouldn't exist yet + const allPanels = "n1jR8vnnz" + _, err := helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + require.Error(t, err, "no all-panels dashboard should exist") + require.True(t, apierrors.IsNotFound(err)) + const repo = "local-tmp" // Set up the repository and the file to import. helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "all-panels.json") - localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ "Name": repo, "SyncEnabled": true, }) - _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) + + // We create the repository + _, err = helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) require.NoError(t, err) + // Now, we import it, such that it may exist + // The sync may not be necessary as the sync may have happened automatically at this point + helper.SyncAndWait(t, repo, nil) + // Make sure the repo can read and validate the file obj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json") require.NoError(t, err, "valid path should be fine") resource, _, err := unstructured.NestedMap(obj.Object, "resource") require.NoError(t, err, "missing resource") - action, _, err := unstructured.NestedString(resource, "action") require.NoError(t, err, "invalid action") - require.NotNil(t, resource["file"], "the raw file") require.NotNil(t, resource["dryRun"], "dryRun result") - require.Equal(t, "create", action) - // But the dashboard shouldn't exist yet - const allPanels = "n1jR8vnnz" - _, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) - require.Error(t, err, "no all-panels dashboard should exist") - require.True(t, apierrors.IsNotFound(err)) - - // Now, we import it, such that it may exist - helper.SyncAndWait(t, repo, nil) + action, _, err := unstructured.NestedString(resource, "action") + require.NoError(t, err, "invalid action") + // FIXME: there is no point in in returning action for a read / get request. + require.Equal(t, "update", action) _, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") @@ -609,9 +654,13 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { _, err = helper.DashboardsV1.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) require.NoError(t, err, "should be able to create v1 dashboard") - dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v2.yaml") - _, err = helper.DashboardsV2.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) - require.NoError(t, err, "should be able to create v2 dashboard") + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v2alpha1.yaml") + _, err = helper.DashboardsV2alpha1.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v2alpha1 dashboard") + + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v2alpha2.yaml") + _, err = helper.DashboardsV2alpha2.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v2alpha2 dashboard") // Now for the repository. const repo = "local-repository" @@ -642,15 +691,19 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { title string apiVersion string name string + fileName string } + printFileTree(t, helper.ProvisioningPath) + // Check that each file was exported with its stored version for _, test := range []props{ - {title: "Test dashboard. Created at v0", apiVersion: "dashboard.grafana.app/v0alpha1", name: "test-v0"}, - {title: "Test dashboard. Created at v1", apiVersion: "dashboard.grafana.app/v1beta1", name: "test-v1"}, - {title: "Test dashboard. Created at v2", apiVersion: "dashboard.grafana.app/v2alpha1", name: "test-v2"}, + {title: "Test dashboard. Created at v0", apiVersion: "dashboard.grafana.app/v0alpha1", name: "test-v0", fileName: "test-dashboard-created-at-v0.json"}, + {title: "Test dashboard. Created at v1", apiVersion: "dashboard.grafana.app/v1beta1", name: "test-v1", fileName: "test-dashboard-created-at-v1.json"}, + {title: "Test dashboard. Created at v2alpha1", apiVersion: "dashboard.grafana.app/v2alpha1", name: "test-v2alpha1", fileName: "test-dashboard-created-at-v2alpha1.json"}, + {title: "Test dashboard. Created at v2alpha2", apiVersion: "dashboard.grafana.app/v2alpha2", name: "test-v2alpha2", fileName: "test-dashboard-created-at-v2alpha2.json"}, } { - fpath := filepath.Join(helper.ProvisioningPath, slugify.Slugify(test.title)+".json") + fpath := filepath.Join(helper.ProvisioningPath, test.fileName) //nolint:gosec // we are ok with reading files in testdata body, err := os.ReadFile(fpath) require.NoError(t, err, "exported file was not created at path %s", fpath) @@ -691,46 +744,16 @@ func TestIntegrationProvisioning_DeleteResources(t *testing.T) { _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) require.NoError(t, err) - // create the structure: - // dashboard1.json - // folder/ - // dashboard2.json - // nested/ - // dashboard3.json - dashboard1 := helper.LoadFile("testdata/all-panels.json") - result := helper.AdminREST.Post(). - Namespace("default"). - Resource("repositories"). - Name(repo). - SubResource("files", "dashboard1.json"). - Body(dashboard1). - SetHeader("Content-Type", "application/json"). - Do(ctx) - require.NoError(t, result.Error()) - dashboard2 := helper.LoadFile("testdata/text-options.json") - result = helper.AdminREST.Post(). - Namespace("default"). - Resource("repositories"). - Name(repo). - SubResource("files", "folder", "dashboard2.json"). - Body(dashboard2). - SetHeader("Content-Type", "application/json"). - Do(ctx) - require.NoError(t, result.Error()) - dashboard3 := helper.LoadFile("testdata/timeline-demo.json") - result = helper.AdminREST.Post(). - Namespace("default"). - Resource("repositories"). - Name(repo). - SubResource("files", "folder", "nested", "dashboard3.json"). - Body(dashboard3). - SetHeader("Content-Type", "application/json"). - Do(ctx) - require.NoError(t, result.Error()) - + // Copy the dashboards to the repository path + helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "dashboard1.json") + helper.CopyToProvisioningPath(t, "testdata/text-options.json", "folder/dashboard2.json") + helper.CopyToProvisioningPath(t, "testdata/timeline-demo.json", "folder/nested/dashboard3.json") // make sure we don't fail when there is a .keep file in a folder helper.CopyToProvisioningPath(t, "testdata/.keep", "folder/nested/.keep") + // Trigger and wait for a sync job to finish + helper.SyncAndWait(t, repo, nil) + dashboards, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err) require.Equal(t, 3, len(dashboards.Items)) @@ -798,3 +821,438 @@ func TestIntegrationProvisioning_DeleteResources(t *testing.T) { require.Error(t, result.Error()) }) } + +func TestIntegrationProvisioning_DeleteJob(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "delete-job-test-repo" + localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repo, + "SyncEnabled": true, + "SyncTarget": "instance", + }) + _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) + require.NoError(t, err) + // Copy multiple test files to the repository + helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "dashboard1.json") + helper.CopyToProvisioningPath(t, "testdata/text-options.json", "dashboard2.json") + helper.CopyToProvisioningPath(t, "testdata/timeline-demo.json", "folder/dashboard3.json") + + // Trigger and wait for initial sync to populate resources + helper.SyncAndWait(t, repo, nil) + + // Verify initial state - should have 3 dashboards and 1 folder + dashboards, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, 3, len(dashboards.Items), "should have 3 dashboards after sync") + + folders, err := helper.Folders.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, 1, len(folders.Items), "should have 1 folder after sync") + + t.Run("delete single file", func(t *testing.T) { + // Create delete job for single file + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("jobs"). + Body(asJSON(&provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"dashboard1.json"}, + }, + })). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result.Error(), "should be able to create delete job") + + // Wait for job to complete + helper.AwaitJobs(t, repo) + + // Verify file is deleted from repository + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "dashboard1.json") + require.Error(t, err, "file should be deleted from repository") + require.True(t, apierrors.IsNotFound(err), "should be not found error") + + // Verify dashboard is removed from Grafana after sync + dashboards, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, 2, len(dashboards.Items), "should have 2 dashboards after delete") + + // Verify other files still exist + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "dashboard2.json") + require.NoError(t, err, "other files should still exist") + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder", "dashboard3.json") + require.NoError(t, err, "nested files should still exist") + }) + + t.Run("delete multiple files", func(t *testing.T) { + // Create delete job for multiple files + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("jobs"). + Body(asJSON(&provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"dashboard2.json", "folder/dashboard3.json"}, + }, + })). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result.Error(), "should be able to create delete job") + + // Wait for job to complete + helper.AwaitJobs(t, repo) + + // Verify files are deleted from repository + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "dashboard2.json") + require.Error(t, err, "dashboard2.json should be deleted") + require.True(t, apierrors.IsNotFound(err)) + + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder", "dashboard3.json") + require.Error(t, err, "folder/dashboard3.json should be deleted") + require.True(t, apierrors.IsNotFound(err)) + + // Verify all dashboards are removed from Grafana after sync + dashboards, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, 0, len(dashboards.Items), "should have 0 dashboards after deleting all") + }) + + t.Run("delete non-existent file", func(t *testing.T) { + // Create delete job for non-existent file + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("jobs"). + Body(asJSON(&provisioning.JobSpec{ + Action: provisioning.JobActionDelete, + Delete: &provisioning.DeleteJobOptions{ + Paths: []string{"non-existent.json"}, + }, + })). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result.Error(), "should be able to create delete job") + + // Wait for job to complete - should fail due to strict error handling + require.EventuallyWithT(t, func(collect *assert.CollectT) { + list := &unstructured.UnstructuredList{} + err := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("jobs"). + Do(ctx).Into(list) + assert.NoError(collect, err, "should be able to list jobs") + assert.NotEmpty(collect, list.Items, "expect at least one job") + + // Find the delete job specifically + var deleteJob *unstructured.Unstructured + for _, elem := range list.Items { + assert.Equal(collect, repo, elem.GetLabels()["provisioning.grafana.app/repository"], "should have repo label") + + action := mustNestedString(elem.Object, "spec", "action") + if action == "delete" { + deleteJob = &elem + break + } + } + assert.NotNil(collect, deleteJob, "should find a delete job") + + state := mustNestedString(deleteJob.Object, "status", "state") + assert.Equal(collect, "error", state, "delete job should have failed due to non-existent file") + }, time.Second*10, time.Millisecond*100, "Expected delete job to fail with error state") + }) +} + +func TestIntegrationProvisioning_MoveResources(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + helper := runGrafana(t) + ctx := context.Background() + const repo = "move-test-repo" + localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repo, + "SyncEnabled": true, + "SyncTarget": "instance", + }) + _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) + require.NoError(t, err) + + // Copy test dashboards to the repository path for initial setup + const originalDashboard = "all-panels.json" + helper.CopyToProvisioningPath(t, "testdata/all-panels.json", originalDashboard) + + // Wait for sync to ensure the dashboard is created in Grafana + helper.SyncAndWait(t, repo, nil) + + // Verify the original dashboard exists in Grafana (using the UID from all-panels.json) + const allPanelsUID = "n1jR8vnnz" // This is the UID from the all-panels.json file + obj, err := helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{}) + require.NoError(t, err, "original dashboard should exist in Grafana") + require.Equal(t, repo, obj.GetAnnotations()[utils.AnnoKeyManagerIdentity]) + + t.Run("move file without content change", func(t *testing.T) { + const targetPath = "moved/simple-move.json" + + // Perform the move operation using helper function + resp := helper.postFilesRequest(t, repo, filesPostOptions{ + targetPath: targetPath, + originalPath: originalDashboard, + message: "move file without content change", + }) + // nolint:errcheck + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "move operation should succeed") + + // Verify the file moved in the repository + movedObj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "moved", "simple-move.json") + require.NoError(t, err, "moved file should exist in repository") + + // Check the content is preserved (verify it's still the all-panels dashboard) + resource, _, err := unstructured.NestedMap(movedObj.Object, "resource") + require.NoError(t, err) + dryRun, _, err := unstructured.NestedMap(resource, "dryRun") + require.NoError(t, err) + title, _, err := unstructured.NestedString(dryRun, "spec", "title") + require.NoError(t, err) + require.Equal(t, "Panel tests - All panels", title, "content should be preserved") + + // Verify original file no longer exists + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", originalDashboard) + require.Error(t, err, "original file should no longer exist") + + // Verify dashboard still exists in Grafana with same content but may have updated path references + helper.SyncAndWait(t, repo, nil) + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{}) + require.NoError(t, err, "dashboard should still exist in Grafana after move") + }) + + t.Run("move file to nested path without ref", func(t *testing.T) { + // Test a different scenario: Move a file that was never synced to Grafana + // This might reveal the issue if dashboard creation fails during move + const sourceFile = "never-synced.json" + helper.CopyToProvisioningPath(t, "testdata/timeline-demo.json", sourceFile) + + // DO NOT sync - move the file immediately without it ever being in Grafana + const targetPath = "deep/nested/timeline.json" + + // Perform the move operation without the file ever being synced to Grafana + resp := helper.postFilesRequest(t, repo, filesPostOptions{ + targetPath: targetPath, + originalPath: sourceFile, + message: "move never-synced file to nested path", + }) + // nolint:errcheck + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "move operation should succeed") + + // Check folders were created and validate hierarchy + folderList, err := helper.Folders.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "should be able to list folders") + + // Build a map of folder names to their objects for easier lookup + folders := make(map[string]*unstructured.Unstructured) + for _, folder := range folderList.Items { + title, _, _ := unstructured.NestedString(folder.Object, "spec", "title") + folders[title] = &folder + parent, _, _ := unstructured.NestedString(folder.Object, "metadata", "annotations", "grafana.app/folder") + t.Logf(" - %s: %s (parent: %s)", folder.GetName(), title, parent) + } + + // Validate expected folders exist with proper hierarchy + // Expected structure: deep -> deep/nested + deepFolderTitle := "deep" + nestedFolderTitle := "nested" + + // Validate "deep" folder exists and has no parent (is top-level) + require.Contains(t, folders, deepFolderTitle, "deep folder should exist") + f := folders[deepFolderTitle] + deepFolderName := f.GetName() + title, _, _ := unstructured.NestedString(f.Object, "spec", "title") + require.Equal(t, deepFolderTitle, title, "deep folder should have correct title") + parent, found, _ := unstructured.NestedString(f.Object, "metadata", "annotations", "grafana.app/folder") + require.True(t, !found || parent == "", "deep folder should be top-level (no parent)") + + // Validate "deep/nested" folder exists and has "deep" as parent + require.Contains(t, folders, nestedFolderTitle, "nested folder should exist") + f = folders[nestedFolderTitle] + nestedFolderName := f.GetName() + title, _, _ = unstructured.NestedString(f.Object, "spec", "title") + require.Equal(t, nestedFolderTitle, title, "nested folder should have correct title") + parent, _, _ = unstructured.NestedString(f.Object, "metadata", "annotations", "grafana.app/folder") + require.Equal(t, deepFolderName, parent, "nested folder should have deep folder as parent") + + // The key test: Check if dashboard was created in Grafana during move + const timelineUID = "mIJjFy8Kz" + dashboard, err := helper.DashboardsV1.Resource.Get(ctx, timelineUID, metav1.GetOptions{}) + require.NoError(t, err, "dashboard should exist in Grafana after moving never-synced file") + dashboardFolder, _, _ := unstructured.NestedString(dashboard.Object, "metadata", "annotations", "grafana.app/folder") + + // Validate dashboard is in the correct nested folder + require.Equal(t, nestedFolderName, dashboardFolder, "dashboard should be in the nested folder") + + // Verify the file moved in the repository + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "deep", "nested", "timeline.json") + require.NoError(t, err, "moved file should exist in nested repository path") + + // Verify the original file no longer exists in the repository + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", sourceFile) + require.Error(t, err, "original file should no longer exist in repository") + }) + + t.Run("move file with content update", func(t *testing.T) { + const sourcePath = "moved/simple-move.json" // Use the file from previous test + const targetPath = "updated/content-updated.json" + + // Use text-options.json content for the update + updatedContent := helper.LoadFile("testdata/text-options.json") + + // Perform move with content update using helper function + resp := helper.postFilesRequest(t, repo, filesPostOptions{ + targetPath: targetPath, + originalPath: sourcePath, + message: "move file with content update", + body: string(updatedContent), + }) + // nolint:errcheck + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "move with content update should succeed") + + // Verify the moved file has updated content (should now be text-options dashboard) + movedObj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "updated", "content-updated.json") + require.NoError(t, err, "moved file should exist in repository") + + resource, _, err := unstructured.NestedMap(movedObj.Object, "resource") + require.NoError(t, err) + dryRun, _, err := unstructured.NestedMap(resource, "dryRun") + require.NoError(t, err) + title, _, err := unstructured.NestedString(dryRun, "spec", "title") + require.NoError(t, err) + require.Equal(t, "Text options", title, "content should be updated to text-options dashboard") + + // Check it has the expected UID from text-options.json + name, _, err := unstructured.NestedString(dryRun, "metadata", "name") + require.NoError(t, err) + require.Equal(t, "WZ7AhQiVz", name, "should have the UID from text-options.json") + + // Verify source file no longer exists + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "moved", "simple-move.json") + require.Error(t, err, "source file should no longer exist") + + // Sync and verify the updated dashboard exists in Grafana + helper.SyncAndWait(t, repo, nil) + const textOptionsUID = "WZ7AhQiVz" // UID from text-options.json + updatedDashboard, err := helper.DashboardsV1.Resource.Get(ctx, textOptionsUID, metav1.GetOptions{}) + require.NoError(t, err, "updated dashboard should exist in Grafana") + + // Verify the original dashboard was deleted from Grafana + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{}) + require.Error(t, err, "original dashboard should be deleted from Grafana") + require.True(t, apierrors.IsNotFound(err)) + + // Verify the new dashboard has the updated content + updatedTitle, _, err := unstructured.NestedString(updatedDashboard.Object, "spec", "title") + require.NoError(t, err) + require.Equal(t, "Text options", updatedTitle) + }) + + t.Run("move directory", func(t *testing.T) { + // Create some files in a directory first using existing testdata files + helper.CopyToProvisioningPath(t, "testdata/timeline-demo.json", "source-dir/timeline-demo.json") + helper.CopyToProvisioningPath(t, "testdata/text-options.json", "source-dir/text-options.json") + + // Sync to ensure files are recognized + helper.SyncAndWait(t, repo, nil) + + const sourceDir = "source-dir/" + const targetDir = "moved-dir/" + + // Move directory using helper function + resp := helper.postFilesRequest(t, repo, filesPostOptions{ + targetPath: targetDir, + originalPath: sourceDir, + message: "move directory", + }) + // nolint:errcheck + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "directory move should succeed") + + // Verify source directory no longer exists + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "source-dir") + require.Error(t, err, "source directory should no longer exist") + + // Verify target directory and files exist + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "moved-dir", "timeline-demo.json") + require.NoError(t, err, "moved timeline-demo.json should exist") + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "moved-dir", "text-options.json") + require.NoError(t, err, "moved text-options.json should exist") + }) + + t.Run("error cases", func(t *testing.T) { + t.Run("missing originalPath parameter", func(t *testing.T) { + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "target.json"). + Body([]byte(`{"test": "content"}`)). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.Error(t, result.Error(), "should fail without originalPath") + }) + + t.Run("file to directory type mismatch", func(t *testing.T) { + // First create a simple test file without slashes in the path + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "simple-test.json"). + Body(helper.LoadFile("testdata/all-panels.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result.Error(), "should create test file") + + // Now try to move this file to a directory path using helper function + resp := helper.postFilesRequest(t, repo, filesPostOptions{ + targetPath: "target-dir/", + originalPath: "simple-test.json", + message: "test move", + }) + // nolint:errcheck + defer resp.Body.Close() + // Read response body to check error message + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + require.NotEqual(t, http.StatusOK, resp.StatusCode, "should fail when moving file to directory") + require.Contains(t, string(body), "cannot move between file and directory types") + }) + + t.Run("non-existent source file", func(t *testing.T) { + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "target.json"). + Param("originalPath", "non-existent.json"). + Body([]byte("")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.Error(t, result.Error(), "should fail when source file doesn't exist") + }) + }) +} diff --git a/pkg/tests/apis/query/query_test.go b/pkg/tests/apis/query/query_test.go deleted file mode 100644 index 4d89c1729c3..00000000000 --- a/pkg/tests/apis/query/query_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package dashboards - -import ( - "context" - "encoding/json" - "net/http" - "testing" - - "github.com/grafana/grafana-plugin-sdk-go/backend" - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/grafana/grafana/pkg/tests/testsuite" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -func TestIntegrationSimpleQuery(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // dev mode required for datasource connections - EnableFeatureToggles: []string{ - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service - }, - }) - - // Create a single datasource - ds := helper.CreateDS(&datasources.AddDataSourceCommand{ - Name: "test", - Type: datasources.DS_TESTDATA, - UID: "test", - OrgID: int64(1), - }) - require.Equal(t, "test", ds.UID) - - t.Run("Call query with expression", func(t *testing.T) { - client := helper.Org1.Admin.RESTClient(t, &schema.GroupVersion{ - Group: "query.grafana.app", - Version: "v0alpha1", - }) - - body, err := json.Marshal(&data.QueryDataRequest{ - Queries: []data.DataQuery{ - data.NewDataQuery(map[string]any{ - "refId": "X", - "datasource": data.DataSourceRef{ - Type: "grafana-testdata-datasource", - UID: ds.UID, - }, - "scenarioId": "csv_content", - "csvContent": "a\n1", - }), - data.NewDataQuery(map[string]any{ - "refId": "Y", - "datasource": data.DataSourceRef{ - UID: "__expr__", - }, - "type": "math", - "expression": "$X + 2", - }), - }, - }) - - //t.Logf("%s", string(body)) - - require.NoError(t, err) - - result := client.Post(). - Namespace("default"). - Suffix("query"). - SetHeader("Content-type", "application/json"). - Body(body). - Do(context.Background()) - - require.NoError(t, result.Error()) - - contentType := "?" - result.ContentType(&contentType) - require.Equal(t, "application/json", contentType) - - body, err = result.Raw() - require.NoError(t, err) - t.Logf("OUT: %s", string(body)) - - rsp := &backend.QueryDataResponse{} - err = json.Unmarshal(body, rsp) - require.NoError(t, err) - require.Equal(t, 2, len(rsp.Responses)) - - frameX := rsp.Responses["X"].Frames[0] - frameY := rsp.Responses["Y"].Frames[0] - - vX, _ := frameX.Fields[0].ConcreteAt(0) - vY, _ := frameY.Fields[0].ConcreteAt(0) - - require.Equal(t, int64(1), vX) - require.Equal(t, float64(3), vY) // 1 + 2, but always float64 - }) - - t.Run("Gets an error with invalid queries", func(t *testing.T) { - client := helper.Org1.Admin.RESTClient(t, &schema.GroupVersion{ - Group: "query.grafana.app", - Version: "v0alpha1", - }) - - body, err := json.Marshal(&data.QueryDataRequest{ - Queries: []data.DataQuery{ - data.NewDataQuery(map[string]any{ - "refId": "Y", - "datasource": data.DataSourceRef{ - UID: "__expr__", - }, - "type": "math", - "expression": "$X + 2", // invalid X does not exit - }), - }, - }) - require.NoError(t, err) - - result := client.Post(). - Namespace("default"). - Suffix("query"). - SetHeader("Content-type", "application/json"). - Body(body). - Do(context.Background()) - - body, err = result.Raw() - //t.Logf("OUT: %s", string(body)) - - require.Error(t, err, "expecting a 400") - require.JSONEq(t, `{ - "results": { - "A": { - "error": "[sse.dependencyError] did not execute expression [Y] due to a failure of the dependent expression or query [X]", - "status": 400, - "errorSource": "" - } - } - }`, string(body)) - // require.JSONEq(t, `{ - // "status": "Failure", - // "metadata": {}, - // "message": "did not execute expression [Y] due to a failure of the dependent expression or query [X]", - // "reason": "BadRequest", - // "details": { "group": "query.grafana.app" }, - // "code": 400, - // "messageId": "sse.dependencyError", - // "extra": { "depRefId": "X", "refId": "Y" } - // }`, string(body)) - - statusCode := -1 - contentType := "?" - result.ContentType(&contentType) - result.StatusCode(&statusCode) - require.Equal(t, "application/json", contentType) - require.Equal(t, http.StatusBadRequest, statusCode) - }) -} diff --git a/pkg/tsdb/cloudwatch/log_query.go b/pkg/tsdb/cloudwatch/log_query.go index 1867cfa2fca..43237bece10 100644 --- a/pkg/tsdb/cloudwatch/log_query.go +++ b/pkg/tsdb/cloudwatch/log_query.go @@ -202,8 +202,12 @@ func groupResults(results *data.Frame, groupingFieldNames []string, fromSyncQuer groupLabels := generateLabels(groupingFields, i) // set the group key as the display name for sync queries - for j := 1; j < len(newFrame.Fields); j++ { + for j := 0; j < len(newFrame.Fields); j++ { valueField := newFrame.Fields[j] + // the time field might not be the first field so we check it here and skip the field if it is + if valueField.Type().Time() { + continue + } if valueField.Config == nil { valueField.Config = &data.FieldConfig{} } diff --git a/pkg/tsdb/cloudwatch/services/accounts.go b/pkg/tsdb/cloudwatch/services/accounts.go index 599ddcf0911..4689d79b555 100644 --- a/pkg/tsdb/cloudwatch/services/accounts.go +++ b/pkg/tsdb/cloudwatch/services/accounts.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" - "strings" oam "github.com/aws/aws-sdk-go-v2/service/oam" oamtypes "github.com/aws/aws-sdk-go-v2/service/oam/types" + "github.com/aws/smithy-go" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" ) @@ -28,9 +28,9 @@ func (a *AccountsService) GetAccountsForCurrentUserOrRole(ctx context.Context) ( for { response, err := a.ListSinks(ctx, &oam.ListSinksInput{NextToken: nextToken}) if err != nil { - // TODO: this is a bit hacky, figure out how to do it right in v2 - if strings.Contains(err.Error(), "AccessDeniedException") { - return nil, fmt.Errorf("%w: %s", ErrAccessDeniedException, err.Error()) + smithyErr := &smithy.GenericAPIError{} + if errors.As(err, &smithyErr) && smithyErr.Code == "AccessDeniedException" { + return nil, fmt.Errorf("%w: %s", ErrAccessDeniedException, smithyErr.Message) } return nil, fmt.Errorf("ListSinks error: %w", err) } diff --git a/pkg/tsdb/cloudwatch/services/accounts_test.go b/pkg/tsdb/cloudwatch/services/accounts_test.go index ba75ad4a9a0..48d99eda6bc 100644 --- a/pkg/tsdb/cloudwatch/services/accounts_test.go +++ b/pkg/tsdb/cloudwatch/services/accounts_test.go @@ -2,13 +2,13 @@ package services import ( "context" - "errors" "fmt" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/oam" oamtypes "github.com/aws/aws-sdk-go-v2/service/oam/types" + "github.com/aws/smithy-go" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" @@ -20,14 +20,14 @@ import ( func TestHandleGetAccounts(t *testing.T) { t.Run("Should return an error in case of insufficient permissions from ListSinks", func(t *testing.T) { fakeOAMClient := &mocks.FakeOAMClient{} - fakeOAMClient.On("ListSinks", mock.Anything).Return(&oam.ListSinksOutput{}, errors.New("AccessDeniedException")) + fakeOAMClient.On("ListSinks", mock.Anything).Return(&oam.ListSinksOutput{}, fmt.Errorf("%w", &smithy.GenericAPIError{Code: "AccessDeniedException", Message: "this is bad"})) accounts := NewAccountsService(fakeOAMClient) resp, err := accounts.GetAccountsForCurrentUserOrRole(context.Background()) assert.Error(t, err) assert.Nil(t, resp) - assert.Equal(t, "access denied. please check your IAM policy: AccessDeniedException", err.Error()) + assert.Equal(t, "access denied. please check your IAM policy: this is bad", err.Error()) assert.ErrorIs(t, err, ErrAccessDeniedException) }) diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 9f269bd4347..b1f89bb6e6f 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -138,9 +138,6 @@ func TestTimeSeriesQuery(t *testing.T) { func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMetricData_is_called_once_per_grouping_of_queries_by_region(t *testing.T) { /* TODO: This test aims to verify the logic to group regions which has been extracted from ParseMetricDataQueries. It should be replaced by a test at a lower level when grouping by regions is incorporated into a separate business logic layer */ - // FIXME: this test is broken - it only works because we're recovering from the panic that the Mock - // produces - see time_series_query.go line 78. If that recover is commented out, the test fails. - t.Skip("skipping broken test") ds := newTestDatasource() origNewCWClient := NewCWClient @@ -152,6 +149,8 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe NewCWClient = func(aws.Config) models.CWClient { return &mockMetricClient } + oneToTwoHoursAgo := backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)} + twoToThreeHoursAgo := backend.TimeRange{From: time.Now().Add(time.Hour * -3), To: time.Now().Add(time.Hour * -2)} t.Run("Queries with the same region should call GetMetricData 1 time", func(t *testing.T) { mockMetricClient = mocks.MetricsAPI{} @@ -164,7 +163,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe Queries: []backend.DataQuery{ { RefID: "A", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -176,7 +175,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe }, { RefID: "B", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -206,7 +205,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe Queries: []backend.DataQuery{ { RefID: "A", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -218,7 +217,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe }, { RefID: "A2", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -230,7 +229,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe }, { RefID: "B", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -260,7 +259,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe Queries: []backend.DataQuery{ { RefID: "A", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now()}, + TimeRange: twoToThreeHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -272,7 +271,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe }, { RefID: "A2", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now()}, + TimeRange: twoToThreeHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", @@ -284,7 +283,7 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe }, { RefID: "B", - TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + TimeRange: oneToTwoHoursAgo, JSON: json.RawMessage(`{ "type": "timeSeriesQuery", "namespace": "AWS/EC2", diff --git a/pkg/web/webtest/webtest.go b/pkg/web/webtest/webtest.go index 53de522ac35..af4163e0410 100644 --- a/pkg/web/webtest/webtest.go +++ b/pkg/web/webtest/webtest.go @@ -147,6 +147,7 @@ func requestContextMiddleware() web.Middleware { c.RequestNonce = ctx.RequestNonce c.PerfmonTimer = ctx.PerfmonTimer c.LookupTokenErr = ctx.LookupTokenErr + c.UseSessionStorageRedirect = ctx.UseSessionStorageRedirect } next.ServeHTTP(w, r) diff --git a/playwright.config.ts b/playwright.config.ts index 648e464f387..b569bc6feea 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,6 +5,7 @@ import { PluginOptions } from '@grafana/plugin-e2e'; const testDirRoot = 'e2e-playwright'; const pluginDirRoot = path.join(testDirRoot, 'plugin-e2e'); +const DEFAULT_URL = 'http://localhost:3001'; export default defineConfig({ fullyParallel: true, @@ -15,8 +16,11 @@ export default defineConfig({ reporter: [ ['html'], // pretty ], + expect: { + timeout: 10_000, + }, use: { - baseURL: `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`, + baseURL: process.env.GRAFANA_URL ?? DEFAULT_URL, trace: 'retain-on-failure', httpCredentials: { username: 'admin', @@ -26,6 +30,14 @@ export default defineConfig({ permissions: ['clipboard-read', 'clipboard-write'], provisioningRootDir: path.join(process.cwd(), process.env.PROV_DIR ?? 'conf/provisioning'), }, + ...(!process.env.GRAFANA_URL && { + webServer: { + command: 'yarn e2e:plugin:build && ./e2e-playwright/start-server', + url: DEFAULT_URL, + stdout: 'pipe', + stderr: 'pipe', + }, + }), projects: [ // Login to Grafana with admin user and store the cookie on disk for use in other tests { diff --git a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts index 77a65889cca..20a6c603f32 100644 --- a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts +++ b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts @@ -161,6 +161,7 @@ const injectedRtkApi = api ref: queryArg.ref, message: queryArg.message, skipDryRun: queryArg.skipDryRun, + originalPath: queryArg.originalPath, }, }), invalidatesTags: ['Repository'], @@ -177,6 +178,7 @@ const injectedRtkApi = api ref: queryArg.ref, message: queryArg.message, skipDryRun: queryArg.skipDryRun, + originalPath: queryArg.originalPath, }, }), invalidatesTags: ['Repository'], @@ -192,6 +194,7 @@ const injectedRtkApi = api ref: queryArg.ref, message: queryArg.message, skipDryRun: queryArg.skipDryRun, + originalPath: queryArg.originalPath, }, }), invalidatesTags: ['Repository'], @@ -527,6 +530,8 @@ export type ReplaceRepositoryFilesWithPathApiArg = { message?: string; /** do not pro-actively verify the payload */ skipDryRun?: boolean; + /** path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -> 'c/d.json') or folder-to-folder (e.g., 'some/' -> 'new/') */ + originalPath?: string; body: { [key: string]: any; }; @@ -543,6 +548,8 @@ export type CreateRepositoryFilesWithPathApiArg = { message?: string; /** do not pro-actively verify the payload */ skipDryRun?: boolean; + /** path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -> 'c/d.json') or folder-to-folder (e.g., 'some/' -> 'new/') */ + originalPath?: string; body: { [key: string]: any; }; @@ -559,6 +566,8 @@ export type DeleteRepositoryFilesWithPathApiArg = { message?: string; /** do not pro-actively verify the payload */ skipDryRun?: boolean; + /** path of file to move (used with POST method for move operations). Must be same type as target path: file-to-file (e.g., 'some/a.json' -> 'c/d.json') or folder-to-folder (e.g., 'some/' -> 'new/') */ + originalPath?: string; }; export type GetRepositoryHistoryApiResponse = /** status 200 OK */ string; export type GetRepositoryHistoryApiArg = { @@ -749,9 +758,17 @@ export type ObjectMeta = { Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ uid?: string; }; +export type DeleteJobOptions = { + /** Paths to be deleted. Examples: - dashboard.json (for a file) - a/b/c/other-dashboard.json (for a file) - nested/deep/ (for a directory) FIXME: we should validate this in admission hooks */ + paths?: string[]; + /** Ref to the branch or commit hash to delete from */ + ref?: string; +}; export type MigrateJobOptions = { /** Preserve history (if possible) */ history?: boolean; + /** Message to use when committing the changes in a single commit */ + message?: string; }; export type PullRequestJobOptions = { /** The specific commit hash that triggered this notice */ @@ -768,20 +785,26 @@ export type SyncJobOptions = { incremental: boolean; }; export type ExportJobOptions = { - /** Target branch for export (only git) */ + /** FIXME: we should validate this in admission hooks Target branch for export (only git) */ branch?: string; /** The source folder (or empty) to export */ folder?: string; + /** Message to use when committing the changes in a single commit */ + message?: string; + /** FIXME: we should validate this in admission hooks Prefix in target file system */ /** Prefix in target file system */ path?: string; }; export type JobSpec = { /** Possible enum values: + - `"delete"` deletes files in the remote repository - `"migrate"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history. - `"pr"` adds additional useful information to a PR, such as comments with preview links and rendered images. - `"pull"` replicates the remote branch in the local copy of the repository. - `"push"` replicates the local copy of the repository in the remote branch. */ - action?: 'migrate' | 'pr' | 'pull' | 'push'; + action?: 'delete' | 'migrate' | 'pr' | 'pull' | 'push'; + /** Delete when the action is `delete` */ + delete?: DeleteJobOptions; /** Required when the action is `migrate` */ migrate?: MigrateJobOptions; /** Pull request options */ @@ -819,8 +842,9 @@ export type JobStatus = { - `"error"` Finished with errors - `"pending"` Job has been submitted, but not processed yet - `"success"` Finished with success + - `"warning"` Finished with some non-critical errors - `"working"` The job is running */ - state?: 'error' | 'pending' | 'success' | 'working'; + state?: 'error' | 'pending' | 'success' | 'warning' | 'working'; /** Summary of processed actions */ summary?: JobResourceSummary[]; }; @@ -991,8 +1015,9 @@ export type SyncStatus = { - `"error"` Finished with errors - `"pending"` Job has been submitted, but not processed yet - `"success"` Finished with success + - `"warning"` Finished with some non-critical errors - `"working"` The job is running */ - state: 'error' | 'pending' | 'success' | 'working'; + state: 'error' | 'pending' | 'success' | 'warning' | 'working'; }; export type WebhookStatus = { encryptedSecret?: string; @@ -1115,8 +1140,9 @@ export type ResourceObjects = { Possible enum values: - `"create"` - `"delete"` + - `"move"` - `"update"` */ - action?: 'create' | 'delete' | 'update'; + action?: 'create' | 'delete' | 'move' | 'update'; /** The value returned from a dryRun request */ dryRun?: Unstructured; /** The same value, currently saved in the grafana database */ diff --git a/public/app/api/clients/provisioning/v0alpha1/index.ts b/public/app/api/clients/provisioning/v0alpha1/index.ts index 986915cebbe..c92490184b1 100644 --- a/public/app/api/clients/provisioning/v0alpha1/index.ts +++ b/public/app/api/clients/provisioning/v0alpha1/index.ts @@ -5,7 +5,15 @@ import { notifyApp } from '../../../../core/actions'; import { createSuccessNotification, createErrorNotification } from '../../../../core/copy/appNotification'; import { createOnCacheEntryAdded } from '../utils/createOnCacheEntryAdded'; -import { generatedAPI, JobSpec, JobStatus, RepositorySpec, RepositoryStatus, ErrorDetails } from './endpoints.gen'; +import { + generatedAPI, + JobSpec, + JobStatus, + RepositorySpec, + RepositoryStatus, + ErrorDetails, + Status, +} from './endpoints.gen'; export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({ endpoints: { @@ -88,7 +96,20 @@ export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({ } else if (e instanceof Error) { dispatch(notifyApp(createErrorNotification('Error validating repository', e))); } else if (typeof e === 'object' && 'error' in e && isFetchError(e.error)) { - if (Array.isArray(e.error.data.errors) && e.error.data.errors.length) { + // Handle Status error responses (Kubernetes style) + if (e.error.data.kind === 'Status' && e.error.data.status === 'Failure') { + const statusError: Status = e.error.data; + dispatch( + notifyApp( + createErrorNotification( + 'Error validating repository', + new Error(statusError.message || 'Unknown error') + ) + ) + ); + } + // Handle TestResults error responses with field errors + else if (Array.isArray(e.error.data.errors) && e.error.data.errors.length) { const nonFieldErrors = e.error.data.errors.filter((err: ErrorDetails) => !err.field); // Only show notification if there are errors that don't have a field, field errors are handled by the form if (nonFieldErrors.length > 0) { diff --git a/public/app/api/utils.ts b/public/app/api/utils.ts index 1ad0ff690e5..5242b18fcfb 100644 --- a/public/app/api/utils.ts +++ b/public/app/api/utils.ts @@ -37,3 +37,15 @@ export const handleError = (e: unknown, dispatch: ThunkDispatch, message: string } } }; + +export function extractErrorMessage(error: unknown): string { + if (error && typeof error === 'object') { + if ('data' in error && error.data && typeof error.data === 'object' && 'message' in error.data) { + return String(error.data.message); + } + if ('message' in error) { + return String(error.message); + } + } + return String(error); +} diff --git a/public/app/app.ts b/public/app/app.ts index ea6400c5837..0e3ce53e238 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -213,16 +213,17 @@ export class GrafanaApp { getPanelPluginFromCache: syncGetPanelPlugin, }); - if (config.featureToggles.useSessionStorageForRedirection) { - handleRedirectTo(); - } - + // Login redirect requires locationUtil to be initialized locationUtil.initialize({ config, getTimeRangeForUrl: getTimeSrv().timeRangeForUrl, getVariablesUrlParams: getVariablesUrlParams, }); + if (config.featureToggles.useSessionStorageForRedirection) { + handleRedirectTo(); + } + // intercept anchor clicks and forward it to custom history instead of relying on browser's history document.addEventListener('click', interceptLinkClicks); @@ -464,7 +465,8 @@ function handleRedirectTo(): void { // In this case there should be a request to the backend window.location.replace(decodedRedirectTo); } else { - locationService.replace(decodedRedirectTo); + const stripped = locationUtil.stripBaseFromUrl(decodedRedirectTo); + locationService.replace(stripped); } } diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 84484eab290..571b264cdd3 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -6,7 +6,7 @@ import { config, getAppEvents, reportInteraction, usePluginLinks, locationServic import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent } from 'app/types/events'; -import { DEFAULT_EXTENSION_SIDEBAR_WIDTH } from './ExtensionSidebar'; +import { DEFAULT_EXTENSION_SIDEBAR_WIDTH, MAX_EXTENSION_SIDEBAR_WIDTH } from './ExtensionSidebar'; export const EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY = 'grafana.navigation.extensionSidebarDocked'; export const EXTENSION_SIDEBAR_WIDTH_LOCAL_STORAGE_KEY = 'grafana.navigation.extensionSidebarWidth'; @@ -231,7 +231,10 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo dockedComponentId, setDockedComponentId: (componentId) => setDockedComponentWithProps(componentId, undefined), availableComponents, - extensionSidebarWidth: extensionSidebarWidth ?? DEFAULT_EXTENSION_SIDEBAR_WIDTH, + extensionSidebarWidth: Math.min( + extensionSidebarWidth ?? DEFAULT_EXTENSION_SIDEBAR_WIDTH, + MAX_EXTENSION_SIDEBAR_WIDTH + ), setExtensionSidebarWidth, props, }} diff --git a/public/app/core/components/AppChrome/TopBar/InviteUserButton.test.tsx b/public/app/core/components/AppChrome/TopBar/InviteUserButton.test.tsx new file mode 100644 index 00000000000..d867c17488b --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/InviteUserButton.test.tsx @@ -0,0 +1,174 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { config, reportInteraction } from '@grafana/runtime'; +import { contextSrv } from 'app/core/core'; +import { getExternalUserMngLinkUrl } from 'app/features/users/utils'; + +import { InviteUserButton } from './InviteUserButton'; + +// Mock dependencies +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + externalUserMngLinkUrl: 'https://example.com/invite', + }, + reportInteraction: jest.fn(), +})); + +jest.mock('app/core/core', () => ({ + contextSrv: { + hasPermission: jest.fn(), + }, +})); + +jest.mock('app/features/users/utils', () => ({ + getExternalUserMngLinkUrl: jest.fn(), +})); + +const mockContextSrv = jest.mocked(contextSrv); +const mockConfig = jest.mocked(config); +const mockReportInteraction = jest.mocked(reportInteraction); +const mockGetExternalUserMngLinkUrl = jest.mocked(getExternalUserMngLinkUrl); + +// Mock window.open +const mockWindowOpen = jest.fn(); +Object.defineProperty(window, 'open', { + value: mockWindowOpen, + writable: true, +}); + +// Mock window.matchMedia for responsive testing +const mockMatchMedia = (matches: boolean) => { + Object.defineProperty(window, 'matchMedia', { + value: jest.fn().mockImplementation(() => ({ + matches, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + })), + writable: true, + }); +}; + +describe('InviteUserButton', () => { + const mockInviteUrl = 'https://example.com/invite?cnt=invite-user-top-bar'; + + beforeEach(() => { + jest.clearAllMocks(); + mockGetExternalUserMngLinkUrl.mockReturnValue(mockInviteUrl); + }); + + describe('Business Logic - When button should appear', () => { + it('should not render when user lacks permission', () => { + mockConfig.externalUserMngLinkUrl = 'https://example.com/invite'; + mockContextSrv.hasPermission.mockReturnValue(false); + mockMatchMedia(true); + + render(); + + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + it('should not render when external user management URL is not configured', () => { + mockConfig.externalUserMngLinkUrl = ''; + mockContextSrv.hasPermission.mockReturnValue(true); + mockMatchMedia(true); + + render(); + + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + }); + + describe('User Experience - Responsive behavior', () => { + beforeEach(() => { + mockConfig.externalUserMngLinkUrl = 'https://example.com/invite'; + mockContextSrv.hasPermission.mockReturnValue(true); + }); + + it('should show text on large screens', () => { + mockMatchMedia(true); // Large screen (≥lg) + + render(); + + const button = screen.getByRole('button', { name: /invite user/i }); + expect(button).toHaveTextContent('Invite'); + }); + + it('should show icon only on small screens', () => { + mockMatchMedia(false); // Small screen (); + + const button = screen.getByRole('button', { name: /invite user/i }); + expect(button).not.toHaveTextContent('Invite'); + }); + }); + + describe('Core Functionality - Click behavior', () => { + beforeEach(() => { + mockConfig.externalUserMngLinkUrl = 'https://example.com/invite'; + mockContextSrv.hasPermission.mockReturnValue(true); + mockMatchMedia(true); + }); + + it('should track analytics and open invite URL when clicked', async () => { + const user = userEvent.setup(); + + render(); + + await user.click(screen.getByRole('button', { name: /invite user/i })); + + // Verify the complete user flow + expect(mockReportInteraction).toHaveBeenCalledWith('invite_user_button_clicked', { + placement: 'top_bar_right', + }); + expect(mockGetExternalUserMngLinkUrl).toHaveBeenCalledWith('invite-user-top-bar'); + expect(mockWindowOpen).toHaveBeenCalledWith(mockInviteUrl, '_blank'); + }); + }); + + describe('Error Handling - Preventing crashes', () => { + beforeEach(() => { + mockConfig.externalUserMngLinkUrl = 'https://example.com/invite'; + mockContextSrv.hasPermission.mockReturnValue(true); + mockMatchMedia(true); + }); + + it('should handle URL generation errors gracefully', async () => { + mockGetExternalUserMngLinkUrl.mockImplementation(() => { + throw new Error('URL generation failed'); + }); + + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const user = userEvent.setup(); + + render(); + + // Should not crash when URL generation fails + await user.click(screen.getByRole('button', { name: /invite user/i })); + + expect(consoleSpy).toHaveBeenCalledWith('Failed to handle invite user click:', expect.any(Error)); + + consoleSpy.mockRestore(); + }); + + it('should handle popup blocking gracefully', async () => { + mockWindowOpen.mockImplementation(() => { + throw new Error('Popup blocked'); + }); + + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const user = userEvent.setup(); + + render(); + + // Should not crash when popup is blocked + await user.click(screen.getByRole('button', { name: /invite user/i })); + + expect(consoleSpy).toHaveBeenCalledWith('Failed to handle invite user click:', expect.any(Error)); + + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx b/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx new file mode 100644 index 00000000000..deec044c42e --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/InviteUserButton.tsx @@ -0,0 +1,43 @@ +import { t } from '@grafana/i18n'; +import { config, reportInteraction } from '@grafana/runtime'; +import { ToolbarButton } from '@grafana/ui'; +import { contextSrv } from 'app/core/core'; +import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth'; +import { getExternalUserMngLinkUrl } from 'app/features/users/utils'; +import { AccessControlAction } from 'app/types/accessControl'; + +import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator'; + +export function InviteUserButton() { + const isLargeScreen = useMediaQueryMinWidth('lg'); + + const handleClick = () => { + try { + reportInteraction('invite_user_button_clicked', { + placement: 'top_bar_right', + }); + + const url = getExternalUserMngLinkUrl('invite-user-top-bar'); + window.open(url.toString(), '_blank'); + } catch (error) { + console.error('Failed to handle invite user click:', error); + } + }; + + const shouldRender = config.externalUserMngLinkUrl && contextSrv.hasPermission(AccessControlAction.OrgUsersAdd); + + return shouldRender ? ( + <> + + {isLargeScreen ? t('navigation.invite-user.invite-button', 'Invite') : undefined} + + + + ) : null; +} diff --git a/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx b/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx new file mode 100644 index 00000000000..4effc8dc94b --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx @@ -0,0 +1,59 @@ +import { render } from 'test/test-utils'; + +import { locationUtil, type GrafanaConfig } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/services/context_srv'; + +import { SignInLink } from './SignInLink'; + +jest.mock('app/core/services/context_srv', () => ({ + contextSrv: { + setRedirectToUrl: jest.fn(), + }, +})); + +describe('SignInLink', () => { + it('should render a link to the login page', () => { + const { getByText } = render(); + const link = getByText('Sign in'); + + expect(link).toHaveAttribute('href', '/?forceLogin=true'); + expect(link).toHaveAttribute('target', '_self'); + }); + + describe('with multiTenantFrontend toggle enabled', () => { + beforeAll(() => { + config.featureToggles.multiTenantFrontend = true; + }); + + it('should render a link to the login page', () => { + const { getByText } = render(); + const link = getByText('Sign in'); + + expect(link).toHaveAttribute('href', '/login'); + expect(link).not.toHaveAttribute('target', '_self'); + }); + + it('remember the redirect url for after login', () => { + const { getByText } = render(); + const link = getByText('Sign in'); + + link.click(); + + expect(contextSrv.setRedirectToUrl).toHaveBeenCalled(); + }); + + it('renders the app base url when served from a subpath', () => { + locationUtil.initialize({ + config: { appSubUrl: '/subpath' } as GrafanaConfig, + getTimeRangeForUrl: () => ({ from: 'now-1d', to: 'now' }), + getVariablesUrlParams: () => ({}), + }); + + const { getByText } = render(); + const link = getByText('Sign in'); + + expect(link).toHaveAttribute('href', '/subpath/login'); + }); + }); +}); diff --git a/public/app/core/components/AppChrome/TopBar/SignInLink.tsx b/public/app/core/components/AppChrome/TopBar/SignInLink.tsx index a7018533763..7c93f089741 100644 --- a/public/app/core/components/AppChrome/TopBar/SignInLink.tsx +++ b/public/app/core/components/AppChrome/TopBar/SignInLink.tsx @@ -1,22 +1,32 @@ import { css } from '@emotion/css'; +import { useCallback } from 'react'; import { useLocation } from 'react-router-dom-v5-compat'; import { GrafanaTheme2, locationUtil, textUtil } from '@grafana/data'; import { Trans } from '@grafana/i18n'; +import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; export function SignInLink() { + const femt = Boolean(config.featureToggles.multiTenantFrontend); const location = useLocation(); const styles = useStyles2(getStyles); - let loginUrl = textUtil.sanitizeUrl(locationUtil.getUrlForPartial(location, { forceLogin: 'true' })); + let loginUrl = femt + ? locationUtil.assureBaseUrl('/login') + : textUtil.sanitizeUrl(locationUtil.getUrlForPartial(location, { forceLogin: 'true' })); // Fix for loginUrl starting with "//" which is a scheme relative URL if (loginUrl.startsWith('//')) { loginUrl = loginUrl.replace(/\/+/g, '/'); } + const handleOnClick = useCallback(() => { + contextSrv.setRedirectToUrl(); + }, []); + return ( - + Sign in ); diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx index 6766d757b49..e5033c75c1c 100644 --- a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx @@ -24,6 +24,7 @@ import { enrichHelpItem } from '../MegaMenu/utils'; import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator'; import { QuickAdd } from '../QuickAdd/QuickAdd'; +import { InviteUserButton } from './InviteUserButton'; import { ProfileButton } from './ProfileButton'; import { SignInLink } from './SignInLink'; import { SingleTopBarActions } from './SingleTopBarActions'; @@ -106,6 +107,7 @@ export const SingleTopBar = memo(function SingleTopBar({ {config.featureToggles.extensionSidebar && !isSmallScreen && } {!showToolbarLevel && actions} {!contextSrv.user.isSignedIn && } + {config.featureToggles.inviteUserExperimental && } {profileNode && }
diff --git a/public/app/core/components/Login/LoginPage.test.tsx b/public/app/core/components/Login/LoginPage.test.tsx index 0e2369b0a8d..3c7593637f5 100644 --- a/public/app/core/components/Login/LoginPage.test.tsx +++ b/public/app/core/components/Login/LoginPage.test.tsx @@ -1,5 +1,6 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { render } from 'test/test-utils'; import * as runtimeMock from '@grafana/runtime'; diff --git a/public/app/core/components/Login/LoginPage.tsx b/public/app/core/components/Login/LoginPage.tsx index 5d866750aa4..864bb707363 100644 --- a/public/app/core/components/Login/LoginPage.tsx +++ b/public/app/core/components/Login/LoginPage.tsx @@ -2,13 +2,14 @@ import { css } from '@emotion/css'; // Components -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { Alert, LinkButton, Stack, useStyles2 } from '@grafana/ui'; import { Branding } from 'app/core/components/Branding/Branding'; import { ChangePassword } from '../ForgottenPassword/ChangePassword'; +import { Page } from '../Page/Page'; import LoginCtrl from './LoginCtrl'; import { LoginForm } from './LoginForm'; @@ -24,76 +25,83 @@ const LoginPage = () => { document.title = Branding.AppTitle; return ( - - {({ - loginHint, - passwordHint, - disableLoginForm, - disableUserSignUp, - login, - passwordlessStart, - passwordlessConfirm, - showPasswordlessConfirmation, - isLoggingIn, - changePassword, - skipPasswordChange, - isChangingPassword, - showDefaultPasswordWarning, - loginErrorMessage, - }) => ( - - {!isChangingPassword && !showPasswordlessConfirmation && ( - - {loginErrorMessage && ( - - {loginErrorMessage} - - )} + + + {({ + loginHint, + passwordHint, + disableLoginForm, + disableUserSignUp, + login, + passwordlessStart, + passwordlessConfirm, + showPasswordlessConfirmation, + isLoggingIn, + changePassword, + skipPasswordChange, + isChangingPassword, + showDefaultPasswordWarning, + loginErrorMessage, + }) => ( + + {!isChangingPassword && !showPasswordlessConfirmation && ( + + {loginErrorMessage && ( + + {loginErrorMessage} + + )} - {!disableLoginForm && !config.auth.passwordlessEnabled && ( - - - {!config.auth.disableLogin && ( - - Forgot your password? - - )} - - - )} - {config.auth.passwordlessEnabled && ( - - )} - - {!disableUserSignUp && } - - )} + {!disableLoginForm && !config.auth.passwordlessEnabled && ( + + + {!config.auth.disableLogin && ( + + Forgot your password? + + )} + + + )} + {config.auth.passwordlessEnabled && ( + + )} + + {!disableUserSignUp && } + + )} - {config.auth.passwordlessEnabled && showPasswordlessConfirmation && ( - - - - )} + {config.auth.passwordlessEnabled && showPasswordlessConfirmation && ( + + + + )} - {isChangingPassword && !config.auth.passwordlessEnabled && ( - - skipPasswordChange()} - /> - - )} - - )} - + {isChangingPassword && !config.auth.passwordlessEnabled && ( + + skipPasswordChange()} + /> + + )} + + )} + + ); }; diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index 38d241c61e2..b1ceef4d2e7 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -22,7 +22,7 @@ import { getDOMId, NestedFolderList } from './NestedFolderList'; import Trigger from './Trigger'; import { useFoldersQuery } from './useFoldersQuery'; import { useTreeInteractions } from './useTreeInteractions'; -import { ROOT_FOLDER_ITEM } from './utils'; +import { getRootFolderItem } from './utils'; export interface NestedFolderPickerProps { /* Folder UID to show as selected */ @@ -222,7 +222,7 @@ export function NestedFolderPicker({ // these options are used infrequently that its not a big deal if (!showRootFolder || excludeUIDs?.length) { flatTree = flatTree.filter((item) => { - if (!showRootFolder && item === ROOT_FOLDER_ITEM) { + if (!showRootFolder && item.item.uid === getRootFolderItem().item.uid) { return false; } @@ -266,7 +266,7 @@ export function NestedFolderPicker({ let label = selectedFolder.data?.title; if (value === '') { - label = 'Dashboards'; + label = t('browse-dashboards.folder-picker.root-title', 'Dashboards'); } // Display the folder name and provisioning status when the picker is closed diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx index d715d57c81d..18d83068b5e 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx +++ b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx @@ -7,7 +7,7 @@ import { DashboardsTreeItem } from 'app/features/browse-dashboards/types'; import { DashboardViewItem } from '../../../features/search/types'; import { useFoldersQuery } from './useFoldersQuery'; -import { ROOT_FOLDER_ITEM } from './utils'; +import { getRootFolderItem } from './utils'; const PAGE_SIZE = 10; @@ -105,7 +105,7 @@ describe('useFoldersQuery', () => { function testFn() { const { result } = renderHook(() => useFoldersQuery(true, {})); - expect(result.current.items).toEqual([ROOT_FOLDER_ITEM]); + expect(result.current.items).toEqual([getRootFolderItem()]); expect(result.current.isLoading).toBe(false); act(() => { result.current.requestNextPage(undefined); diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts index 93817d6a9c8..4828417c10c 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts +++ b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts @@ -10,7 +10,7 @@ import { AnnoKeyManagerKind, ManagerKind } from '../../../features/apiserver/typ import { PAGE_SIZE } from '../../../features/browse-dashboards/api/services'; import { getPaginationPlaceholders } from '../../../features/browse-dashboards/state/utils'; -import { ROOT_FOLDER_ITEM } from './utils'; +import { getRootFolderItem } from './utils'; type GetFolderChildrenQuery = ReturnType>; type GetFolderChildrenRequest = { @@ -151,7 +151,7 @@ export function useFoldersQueryAppPlatform(isBrowsing: boolean, openFolders: Rec } const rootFlatTree = createFlatList(rootFolderToken, state.responseByParent[rootFolderToken], 1); - rootFlatTree.unshift(ROOT_FOLDER_ITEM); + rootFlatTree.unshift(getRootFolderItem()); return rootFlatTree; }, [state, isBrowsing, openFolders]); diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts b/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts index 8381c61d1f2..84c540f7f71 100644 --- a/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts +++ b/public/app/core/components/NestedFolderPicker/useFoldersQueryLegacy.ts @@ -11,7 +11,7 @@ import { PermissionLevelString } from 'app/types/acl'; import { FolderListItemDTO } from 'app/types/folders'; import { useDispatch, useSelector } from 'app/types/store'; -import { ROOT_FOLDER_ITEM } from './utils'; +import { getRootFolderItem } from './utils'; type ListFoldersQuery = ReturnType>; type ListFoldersRequest = QueryActionCreatorResult< @@ -179,7 +179,7 @@ export function useFoldersQueryLegacy( } const rootFlatTree = createFlatList(undefined, state.rootPages, 1); - rootFlatTree.unshift(ROOT_FOLDER_ITEM); + rootFlatTree.unshift(getRootFolderItem()); return rootFlatTree; }, [state, isBrowsing, openFolders]); diff --git a/public/app/core/components/NestedFolderPicker/utils.ts b/public/app/core/components/NestedFolderPicker/utils.ts index 9438485ce1d..1d92fc57f00 100644 --- a/public/app/core/components/NestedFolderPicker/utils.ts +++ b/public/app/core/components/NestedFolderPicker/utils.ts @@ -1,9 +1,11 @@ -export const ROOT_FOLDER_ITEM = { +import { t } from '@grafana/i18n'; + +export const getRootFolderItem = () => ({ isOpen: true, level: 0, item: { kind: 'folder' as const, - title: 'Dashboards', + title: t('browse-dashboards.folder-picker.root-title', 'Dashboards'), uid: '', }, -}; +}); diff --git a/public/app/core/components/RolePicker/RolePickerInput.tsx b/public/app/core/components/RolePicker/RolePickerInput.tsx index 9aaa4b447f5..3f8158020c3 100644 --- a/public/app/core/components/RolePicker/RolePickerInput.tsx +++ b/public/app/core/components/RolePicker/RolePickerInput.tsx @@ -116,7 +116,9 @@ export const RolesLabel = ({ showBuiltInRole, numberOfRoles, appliedRoles }: Rol - {appliedRoles?.map((role) =>

{role.group + ':' + (role.displayName || role.name)}

)} + {appliedRoles?.map((role) => ( +

{role.group + ':' + (role.displayName || role.name)}

+ ))}
} > diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index 2991942372b..7afc8df92a6 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -342,7 +342,7 @@ export class SharedPreferences extends PureComponent { Region format - + } description={t( diff --git a/public/app/core/icons/cached.json b/public/app/core/icons/cached.json index 520a7c14941..ce86bef4eae 100644 --- a/public/app/core/icons/cached.json +++ b/public/app/core/icons/cached.json @@ -180,5 +180,7 @@ "unicons/ai-sparkle", "unicons/dollar-alt", "unicons/window-grid", - "unicons/ban" + "unicons/ban", + "unicons/git", + "unicons/bitbucket" ] diff --git a/public/app/core/internationalization/locales.ts b/public/app/core/internationalization/locales.ts index 9b811d39ba6..569bf3e7b5f 100644 --- a/public/app/core/internationalization/locales.ts +++ b/public/app/core/internationalization/locales.ts @@ -4,280 +4,418 @@ interface Locale { code: string; } -// TODO re-check translations +// Grafana does not support right-to-left languages in the UI, so to avoid confusion +// and a poor experience, we've commented out the right-to-left languages so they don't +// appear as options. export const LOCALES: Locale[] = [ // Afrikaans - Standard { name: 'Afrikaans', code: 'af' }, - // Arabic - Standard - { name: 'العربية', code: 'ar' }, - // Arabic - Algeria - { name: 'العربية (الجزائر)', code: 'ar-DZ' }, - // Arabic - Kuwait - { name: 'العربية (الكويت)', code: 'ar-KW' }, - // Arabic - Libya - { name: 'العربية (ليبيا)', code: 'ar-LY' }, - // Arabic - Morocco - { name: 'العربية (المغرب)', code: 'ar-MA' }, - // Arabic - Palestine - { name: 'العربية (فلسطين)', code: 'ar-PS' }, - // Arabic - Saudi Arabia - { name: 'العربية (السعودية)', code: 'ar-SA' }, - // Arabic - Tunisia - { name: 'العربية (تونس)', code: 'ar-TN' }, + + // // Arabic - Standard + // { name: 'العربية', code: 'ar' }, // Disabled because RTL + + // // Arabic - Algeria + // { name: 'العربية (الجزائر)', code: 'ar-DZ' }, // Disabled because RTL + + // // Arabic - Kuwait + // { name: 'العربية (الكويت)', code: 'ar-KW' }, // Disabled because RTL + + // // Arabic - Libya + // { name: 'العربية (ليبيا)', code: 'ar-LY' }, // Disabled because RTL + + // // Arabic - Morocco + // { name: 'العربية (المغرب)', code: 'ar-MA' }, // Disabled because RTL + + // // Arabic - Palestine + // { name: 'العربية (فلسطين)', code: 'ar-PS' }, // Disabled because RTL + + // // Arabic - Saudi Arabia + // { name: 'العربية (السعودية)', code: 'ar-SA' }, // Disabled because RTL + + // // Arabic - Tunisia + // { name: 'العربية (تونس)', code: 'ar-TN' }, // Disabled because RTL + // Azerbaijani - Azerbaijan { name: 'Azərbaycan dili', code: 'az' }, + // Belarusian - Belarus { name: 'Беларуская мова', code: 'be-BY' }, + // Bulgarian - Bulgaria { name: 'Български език', code: 'bg-BG' }, + // Bambara - Mali { name: 'Bamanankan', code: 'bm' }, + // Bengali - Standard { name: 'বাংলা', code: 'bn' }, + // Bengali - Bangladesh { name: 'বাংলা', code: 'bn-BD' }, + // Tibetan - Tibet (China) and Bhutan { name: 'བོད་ཡིག', code: 'bo' }, + // Breton - Brittany (France) { name: 'Brezhoneg', code: 'br' }, + // Bosnian - Bosnia and Herzegovina { name: 'Bosanski jezik', code: 'bs' }, + // Catalan - Catalonia (Spain) { name: 'Català', code: 'ca-ES' }, + // Czech - Czech Republic { name: 'Čeština', code: 'cs-CZ' }, + // Welsh - Wales (United Kingdom) { name: 'Cymraeg', code: 'cy-GB' }, + // Chuvash - Chuvashia (Russia) { name: 'Чӑвашла', code: 'cv-RU' }, + // Danish - Denmark { name: 'Dansk', code: 'da-DK' }, + // German - Germany { name: 'Deutsch', code: 'de-DE' }, + // German - Austria { name: 'Deutsch (Österreich)', code: 'de-AT' }, + // German - Switzerland { name: 'Deutsch (Schweiz)', code: 'de-CH' }, - // Divehi/Maldivian - Maldives - { name: 'ދިވެހި', code: 'dv-MV' }, + + // // Divehi/Maldivian - Maldives + // { name: 'ދިވެހި', code: 'dv-MV' }, // Disabled because RTL + // Greek - Greece { name: 'Ελληνικά', code: 'el-GR' }, + // English - Australia { name: 'English (Australia)', code: 'en-AU' }, + // English - Canada { name: 'English (Canada)', code: 'en-CA' }, + // English - United Kingdom { name: 'English (United Kingdom)', code: 'en-GB' }, + // English - Ireland { name: 'English (Ireland)', code: 'en-IE' }, + // English - Israel { name: 'English (Israel)', code: 'en-IL' }, + // English - India { name: 'English (India)', code: 'en-IN' }, + // English - New Zealand { name: 'English (New Zealand)', code: 'en-NZ' }, + // English - Singapore { name: 'English (Singapore)', code: 'en-SG' }, + // English - United States { name: 'English (United States)', code: 'en-US' }, + // Esperanto - International Auxiliary Language - { name: 'Esperanto', code: 'eo' }, + // { name: 'Esperanto', code: 'eo' }, + // Spanish - Spain { name: 'Español', code: 'es-ES' }, + // Spanish - Dominican Republic { name: 'Español (República Dominicana)', code: 'es-DO' }, + // Spanish - Mexico { name: 'Español (México)', code: 'es-MX' }, + // Spanish - United States { name: 'Español (Estados Unidos)', code: 'es-US' }, + // Estonian - Estonia { name: 'Eesti keel', code: 'et-EE' }, + // Basque - Basque Country { name: 'Euskara', code: 'eu-ES' }, - // Persian - Iran - { name: 'فارسی', code: 'fa-IR' }, + + // // Persian - Iran + // { name: 'فارسی', code: 'fa-IR' }, // Disabled because RTL + // Filipino - Philippines { name: 'Wikang Filipino', code: 'fil-PH' }, + // Finnish - Finland { name: 'Suomi', code: 'fi-FI' }, + // Faroese - Faroe Islands { name: 'Føroyskt', code: 'fo-FO' }, + // French - France { name: 'Français', code: 'fr-FR' }, + // French - Canada { name: 'Français (Canada)', code: 'fr-CA' }, + // French - Switzerland { name: 'Français (Suisse)', code: 'fr-CH' }, + // West Frisian - Netherlands { name: 'Frysk', code: 'fy' }, + // Irish - Ireland { name: 'Gaeilge', code: 'ga-IE' }, + // Scottish Gaelic - Scotland (UK) { name: 'Gàidhlig', code: 'gd-GB' }, + // Galician - Galicia (Spain) { name: 'Galego', code: 'gl-ES' }, + // Konkani (Devanagari script) - India { name: 'कोंकणी', code: 'gom-Deva' }, + // Konkani (Latin script) - India { name: 'Konkani', code: 'gom-Latn' }, + // Gujarati - India { name: 'ગુજરાતી', code: 'gu-IN' }, - // Hebrew - Israel - { name: 'עברית', code: 'he-IL' }, + + // // Hebrew - Israel + // { name: 'עברית', code: 'he-IL' }, // Disabled because RTL + // Hindi - India { name: 'हिन्दी', code: 'hi' }, + // Croatian - Croatia { name: 'Hrvatski jezik', code: 'hr' }, + // Hungarian - Hungary { name: 'Magyar nyelv', code: 'hu-HU' }, + // Armenian - Armenia { name: 'Հայերեն', code: 'hy-AM' }, + // Indonesian - Indonesia { name: 'Bahasa Indonesia', code: 'id-ID' }, + // Icelandic - Iceland { name: 'Íslenska', code: 'is-IS' }, + // Italian - Italy { name: 'Italiano', code: 'it-IT' }, + // Italian - Switzerland { name: 'Italiano (Svizzera)', code: 'it-CH' }, + // Japanese - Japan { name: '日本語', code: 'ja-JP' }, + // Javanese - Indonesia { name: 'ꦧꦱꦗꦮ', code: 'jv' }, + // Georgian - Georgia { name: 'ქართული ენა', code: 'ka-GE' }, + // Kazakh - Kazakhstan { name: 'Қазақ тілі', code: 'kk-KZ' }, + // Khmer - Cambodia { name: 'ខ្មែរ', code: 'km-KH' }, + // Kannada - India { name: 'ಕನ್ನಡ', code: 'kn-IN' }, + // Korean - South Korea { name: '한국어', code: 'ko-KR' }, + // Kurdish - Kurdistan (Iraq, Iran, Syria, Turkey) { name: 'Kurdî', code: 'ku' }, + // Kyrgyz - Kyrgyzstan { name: 'Кыргыз тили', code: 'ky-KG' }, + // Luxembourgish - Luxembourg { name: 'Lëtzebuergesch', code: 'lb-LU' }, + // Lao - Laos { name: 'ພາສາລາວ', code: 'lo-LA' }, + // Lithuanian - Lithuania { name: 'Lietuvių kalba', code: 'lt-LT' }, + // Latvian - Latvia { name: 'Latviešu valoda', code: 'lv-LV' }, + // Macedonian - North Macedonia { name: 'Македонски јазик', code: 'mk-MK' }, + // Malayalam - Kerala (India) { name: 'മലയാളം', code: 'ml-IN' }, + // Māori - New Zealand { name: 'Te Reo Māori', code: 'mi-NZ' }, + // Montenegrin - Montenegro { name: 'Црногорски језик', code: 'cnr-ME' }, + // Marathi - Maharashtra (India) { name: 'मराठी', code: 'mr' }, + // Malay - Malaysia, Singapore, Brunei { name: 'Bahasa Melayu', code: 'ms' }, + // Maltese - Malta { name: 'Malti', code: 'mt-MT' }, + // Mongolian - Mongolia { name: 'Монгол хэл', code: 'mn-MN' }, + // Burmese - Myanmar { name: 'မြန်မာစာ', code: 'my-MM' }, + // Norwegian Bokmål - Norway { name: 'Norsk bokmål', code: 'nb' }, + // Nepali - Nepal and India { name: 'नेपाली', code: 'ne' }, + // Dutch - Netherlands { name: 'Nederlands', code: 'nl-NL' }, + // Dutch - Belgium (Flemish) { name: 'Nederlands (België)', code: 'nl-BE' }, + // Norwegian Nynorsk - Norway { name: 'Nynorsk', code: 'nn-NO' }, + // Occitan - Southern France, Monaco, Italy { name: 'Occitan', code: 'oc' }, + // Punjabi - Punjab (India and Pakistan) { name: 'ਪੰਜਾਬੀ', code: 'pa' }, + // Polish - Poland { name: 'Polski', code: 'pl-PL' }, + // Portuguese - Portugal { name: 'Português', code: 'pt-PT' }, + // Portuguese - Brazil { name: 'Português (Brasil)', code: 'pt-BR' }, + // Romanian - Romania { name: 'Română', code: 'ro-RO' }, + // Russian - Russia { name: 'Русский язык', code: 'ru-RU' }, + // Northern Sami - Northern Scandinavia { name: 'Davvisámegiella', code: 'se' }, - // Sindhi - Pakistan and India - { name: 'سنڌي', code: 'sd' }, + + // // Sindhi - Pakistan and India + // { name: 'سنڌي', code: 'sd' }, // Disabled because RTL + // Sinhala - Sri Lanka { name: 'සිංහල', code: 'si-LK' }, + // Slovak - Slovakia { name: 'Slovenský jazyk', code: 'sk-SK' }, + // Slovenian - Slovenia { name: 'Slovenski jezik', code: 'sl-SI' }, + // Albanian - Albania, Kosovo { name: 'Shqip', code: 'sq' }, + // Serbian - Serbia (Default) { name: 'Српски', code: 'sr' }, + // Serbian - Serbia (Cyrillic script) { name: 'Српски (ћирилица)', code: 'sr-Cyrl' }, + // Swati - Eswatini (Swaziland) { name: 'SiSwati', code: 'ss' }, + // Swahili - East Africa { name: 'Kiswahili', code: 'sw' }, + // Swedish - Sweden { name: 'Svenska', code: 'sv' }, + // Tamil - Tamil Nadu (India), Sri Lanka, Singapore { name: 'தமிழ்', code: 'ta' }, + // Telugu - Andhra Pradesh, Telangana (India) { name: 'తెలుగు', code: 'te' }, + // Tetum - East Timor { name: 'Tetun', code: 'tet' }, + // Tajik - Tajikistan { name: 'Тоҷикӣ', code: 'tg' }, + // Thai - Thailand { name: 'ภาษาไทย', code: 'th-TH' }, + // Turkmen - Turkmenistan { name: 'Türkmen dili', code: 'tk-TM' }, + // Tagalog - Philippines { name: 'Wikang Tagalog', code: 'tl-PH' }, + // Klingon - Constructed Language (Star Trek) { name: 'tlhIngan Hol', code: 'tlh' }, + // Turkish - Turkey { name: 'Türkçe', code: 'tr-TR' }, + // Talossan - Constructed Language { name: 'Talossan', code: 'tzl' }, + // Tamazight (Tifinagh script) - North Africa { name: 'ⵜⴰⵎⴰⵣⵉⵖⵜ', code: 'tzm' }, + // Tamazight (Latin script) - North Africa { name: 'Tamazight', code: 'tzm-Latn' }, - // Uyghur - Xinjiang (China) - { name: 'ئۇيغۇرچە', code: 'ug-CN' }, + + // // Uyghur - Xinjiang (China) + // { name: 'ئۇيغۇرچە', code: 'ug-CN' }, // Disabled because RTL + // Ukrainian - Ukraine { name: 'Українська мова', code: 'uk-UA' }, - // Urdu - Pakistan and India - { name: 'اردو', code: 'ur-PK' }, + + // // Urdu - Pakistan and India + // { name: 'اردو', code: 'ur-PK' }, // Disabled because RTL + // Uzbek - Uzbekistan (Cyrillic script) { name: 'Ўзбек тили', code: 'uz-UZ' }, + // Uzbek - Uzbekistan (Latin script) { name: "O'zbek tili", code: 'uz-Latn' }, + // Vietnamese - Vietnam { name: 'Tiếng Việt', code: 'vi-VN' }, + // Chinese - China { name: '中文', code: 'zh-CN' }, + // Chinese - Simplified { name: '简体中文', code: 'zh-Hans' }, + // Chinese - Traditional { name: '繁體中文', code: 'zh-Hant' }, + // Chinese - Hong Kong { name: '中文 (香港)', code: 'zh-HK' }, + // Chinese - Taiwan { name: '正體中文 (台灣)', code: 'zh-TW' }, + // Chinese - Macau { name: '中文 (澳門)', code: 'zh-MO' }, + // Yoruba - Nigeria, Benin, Togo { name: 'Yorùbá', code: 'yo' }, ]; diff --git a/public/app/core/navigation/GrafanaRoute.tsx b/public/app/core/navigation/GrafanaRoute.tsx index 80c31728dcf..c5c751366dc 100644 --- a/public/app/core/navigation/GrafanaRoute.tsx +++ b/public/app/core/navigation/GrafanaRoute.tsx @@ -1,7 +1,7 @@ import { Suspense, useEffect, useLayoutEffect } from 'react'; import { Navigate, useLocation } from 'react-router-dom-v5-compat'; -import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; +import { config, locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; import { ErrorBoundary } from '@grafana/ui'; import { useGrafana } from '../context/GrafanaContext'; @@ -62,6 +62,17 @@ export function GrafanaRoute(props: Props) { export function GrafanaRouteWrapper({ route }: Pick) { const location = useLocation(); + + // Perform login check in the frontend now + if (config.featureToggles.multiTenantFrontend) { + const routeRequiresSignin = !route.allowAnonymous && !config.anonymousEnabled; + if (routeRequiresSignin && !contextSrv.isSignedIn) { + contextSrv.setRedirectToUrl(); + + return ; + } + } + const roles = route.roles ? route.roles() : []; if (roles?.length) { if (!roles.some((r: string) => contextSrv.hasRole(r))) { diff --git a/public/app/core/navigation/types.ts b/public/app/core/navigation/types.ts index 6f1453f77b3..8fafbfa2a8b 100644 --- a/public/app/core/navigation/types.ts +++ b/public/app/core/navigation/types.ts @@ -20,4 +20,10 @@ export interface RouteDescriptor { routeName?: string; chromeless?: boolean; sensitive?: boolean; + + /** + * Allow the route to be access by anonymous users. + * Currently only used if the `multiTenantFrontend` feature toggle is enabled. + */ + allowAnonymous?: boolean; } diff --git a/public/app/features/admin/Users/OrgUnits.tsx b/public/app/features/admin/Users/OrgUnits.tsx index b029df9e19e..f91c54f79d8 100644 --- a/public/app/features/admin/Users/OrgUnits.tsx +++ b/public/app/features/admin/Users/OrgUnits.tsx @@ -14,7 +14,13 @@ export const OrgUnits = ({ units, icon }: OrgUnitProps) => { return units.length > 1 ? ( {units?.map((unit) => {unit.name})}} + content={ + + {units?.map((unit) => ( + {unit.name} + ))} + + } > {units.length} diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index 9b3d6a2a289..032feeabd5d 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -133,7 +133,15 @@ export const Expression: FC = ({ ); case ExpressionQueryType.sql: - return onChangeQuery(query)} query={query} refIds={availableRefIds} alerting />; + return ( + onChangeQuery(query)} + query={query} + refIds={availableRefIds} + alerting + queries={[]} + /> + ); default: return ( diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 0d104bd528f..6f0f358f011 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -107,6 +107,9 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props) const [payload, setPayload] = useState(defaultPayloadString); const [payloadFormatError, setPayloadFormatError] = useState(null); + // AI feedback state + const [aiGeneratedTemplate, setAiGeneratedTemplate] = useState(false); + const { isProvisioned } = useNotificationTemplateMetadata(originalTemplate); const originalTemplatePrefill: TemplateFormValues | undefined = originalTemplate ? { title: originalTemplate.title, content: originalTemplate.content } @@ -167,6 +170,7 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props) const handleTemplateGenerated = (template: string) => { setValue('content', template); + setAiGeneratedTemplate(true); }; return ( @@ -352,6 +356,8 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props) setPayloadFormatError={setPayloadFormatError} payloadFormatError={payloadFormatError} className={cx(styles.templatePreview, styles.minEditorSize)} + aiGeneratedTemplate={aiGeneratedTemplate} + setAiGeneratedTemplate={setAiGeneratedTemplate} />
)} diff --git a/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx b/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx index 8745e12effd..fc6014a0e0e 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx @@ -8,6 +8,7 @@ import { Trans, t } from '@grafana/i18n'; import { Alert, Box, Button, CodeEditor, useStyles2 } from '@grafana/ui'; import { TemplatePreviewErrors, TemplatePreviewResponse, TemplatePreviewResult } from '../../api/templateApi'; +import { AIFeedbackButtonComponent } from '../../enterprise-components/AI/addAIFeedbackButton'; import { stringifyErrorLike } from '../../utils/misc'; import { EditorColumnHeader } from '../contact-points/templates/EditorColumnHeader'; @@ -20,6 +21,8 @@ export function TemplatePreview({ payloadFormatError, setPayloadFormatError, className, + aiGeneratedTemplate, + setAiGeneratedTemplate, }: { payload: string; templateName: string; @@ -27,6 +30,8 @@ export function TemplatePreview({ payloadFormatError: string | null; setPayloadFormatError: (value: React.SetStateAction) => void; className?: string; + aiGeneratedTemplate?: boolean; + setAiGeneratedTemplate?: (aiGeneratedTemplate: boolean) => void; }) { const styles = useStyles2(getStyles); @@ -48,7 +53,10 @@ export function TemplatePreview({ disabled={isLoading} icon="sync" aria-label={t('alerting.template-preview.aria-label-refresh-preview', 'Refresh preview')} - onClick={onPreview} + onClick={() => { + onPreview(); + setAiGeneratedTemplate?.(false); + }} size="sm" variant="secondary" > @@ -56,6 +64,9 @@ export function TemplatePreview({ } /> +
+ +
{({ height }) =>
{previewToRender}
} @@ -163,6 +174,22 @@ const getStyles = (theme: GrafanaTheme2) => ({ errorText: css({ color: theme.colors.error.text, }), + feedbackContainer: css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderTop: `1px solid ${theme.colors.border.medium}`, + backgroundColor: theme.colors.background.secondary, + minHeight: 'auto', + }), + emptyState: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + color: theme.colors.text.secondary, + fontSize: theme.typography.bodySmall.fontSize, + }), }, }); @@ -174,10 +201,12 @@ export function getPreviewResults( // ERRORS IN JSON OR IN REQUEST (endpoint not available, for example) const previewErrorRequest = previewError ? stringifyErrorLike(previewError) : undefined; const errorToRender = payloadFormatError || previewErrorRequest; + const styles = useStyles2(getStyles); //PREVIEW : RESULTS AND ERRORS const previewResponseResults = data?.results ?? []; const previewResponseErrors = data?.errors; + const hasContent = previewResponseResults.length > 0 || previewResponseErrors || errorToRender; return ( <> @@ -187,7 +216,12 @@ export function getPreviewResults( )} {previewResponseErrors && } - {previewResponseResults && } + {previewResponseResults.length > 0 && } + {!hasContent && ( +
+ Add template content to see preview +
+ )} ); } diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 0a16d0022a0..3d70f60f79a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -40,6 +40,7 @@ import { RulerGroupUpdatedResponse, isGrafanaGroupUpdatedResponse, } from '../../../api/alertRuleModel'; +import { AIFeedbackButtonComponent } from '../../../enterprise-components/AI/addAIFeedbackButton'; import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup'; import { defaultFormValuesForRuleType, @@ -244,6 +245,10 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => {/* Step 1 */} + + {/* AI Feedback: automatically shown for rules generated by AI */} + + {/* Step 2 */} {/* Step 3-4-5 */} diff --git a/public/app/features/alerting/unified/enterprise-components/AI/addAIFeedbackButton.tsx b/public/app/features/alerting/unified/enterprise-components/AI/addAIFeedbackButton.tsx new file mode 100644 index 00000000000..d0d68bcd0e8 --- /dev/null +++ b/public/app/features/alerting/unified/enterprise-components/AI/addAIFeedbackButton.tsx @@ -0,0 +1,44 @@ +import { ComponentType, createElement } from 'react'; + +import { t } from '@grafana/i18n'; +import { withErrorBoundary } from '@grafana/ui'; + +import { logError } from '../../Analytics'; + +export type AIFeedbackOrigin = 'alert-rule' | 'template' | 'triage'; + +export interface GenAIFeedbackButtonProps { + origin: AIFeedbackOrigin; + shouldShowFeedbackButton?: boolean; + // If true, the component will use the route detection to determine if the feedback should be shown + // this is necessary for example in alerting rule form, where the feedback button is shown in the same page as the rule form + useRouteDetection?: boolean; +} + +let InternalAIFeedbackButtonComponent: ComponentType | null = null; + +export const AIFeedbackButtonComponent: ComponentType = (props) => { + if (!InternalAIFeedbackButtonComponent) { + return null; + } + + // Wrap the component with error boundary + const WrappedComponent = withErrorBoundary(InternalAIFeedbackButtonComponent, { + title: t('alerting.ai.error-boundary.feedback-button', 'AI Feedback Button failed to load'), + style: 'alertbox', + errorLogger: logError, + }); + + // Provide default value for shouldShowFeedbackButton + const propsWithDefaults = { + shouldShowFeedbackButton: true, + useRouteDetection: false, + ...props, + }; + + return createElement(WrappedComponent, propsWithDefaults); +}; + +export function addAIFeedbackButton(component: ComponentType | null) { + InternalAIFeedbackButtonComponent = component; +} diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx index 2dee977835d..7d9a8d184de 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx @@ -1,13 +1,20 @@ +import { css } from '@emotion/css'; +import { useMemo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { Alert } from '@grafana/ui'; +import { Alert, Pagination, Stack, useStyles2 } from '@grafana/ui'; import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { prometheusApi } from '../api/prometheusApi'; +import { usePagination } from '../hooks/usePagination'; import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants'; import { GrafanaRuleListItem } from './GrafanaRuleListItem'; import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader'; +const DEFAULT_PER_PAGE_PAGINATION_RULES_PER_GROUP_LIST_VIEW_V2 = 100; + const { useGetGrafanaGroupsQuery } = prometheusApi; export interface GrafanaGroupLoaderProps { @@ -31,6 +38,8 @@ export function GrafanaGroupLoader({ namespaceName, expectedRulesCount = 3, // 3 is a random number. Usually we get the number of rules from Prometheus response }: GrafanaGroupLoaderProps) { + const styles = useStyles2(getStyles); + const { data: promResponse, isLoading: isPromResponseLoading } = useGetGrafanaGroupsQuery( { folderUid: groupIdentifier.namespace.uid, @@ -40,6 +49,16 @@ export function GrafanaGroupLoader({ { pollingInterval: RULE_LIST_POLL_INTERVAL_MS } ); + const rules = useMemo(() => { + return promResponse?.data.groups.at(0)?.rules ?? []; + }, [promResponse]); + + const { pageItems, page, numberOfPages, onPageChange } = usePagination( + rules, + 1, + DEFAULT_PER_PAGE_PAGINATION_RULES_PER_GROUP_LIST_VIEW_V2 + ); + if (isPromResponseLoading) { return ( <> @@ -63,9 +82,14 @@ export function GrafanaGroupLoader({ ); } + // If no rules found, return early without pagination + if (rules.length === 0) { + return ; + } + return ( - <> - {promResponse.data.groups.at(0)?.rules.map((promRule) => { + + {pageItems.map((promRule) => { return ( ); })} - +
+ {numberOfPages > 1 && ( + + )} +
+
); } + +const getStyles = (theme: GrafanaTheme2) => ({ + pagination: css({ + display: 'flex', + margin: 0, + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(0.25), + justifyContent: 'center', + float: 'none', + }), + paginationWrapper: css({ + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + marginLeft: theme.spacing(2.5), + }), +}); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx index c716c6cbb37..bec19c1cc99 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx @@ -1,16 +1,13 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useLocation } from 'react-router-dom-v5-compat'; import { useAsyncFn, useInterval } from 'react-use'; -import { urlUtil } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; -import { logInfo } from '@grafana/runtime'; -import { Button, LinkButton, Stack } from '@grafana/ui'; +import { t } from '@grafana/i18n'; +import { Button, Stack } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useDispatch } from 'app/types/store'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; -import { LogMessages, trackRuleListNavigation } from '../Analytics'; +import { trackRuleListNavigation } from '../Analytics'; import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import RulesFilter from '../components/rules/Filter/RulesFilter.v1'; import { NoRulesSplash } from '../components/rules/NoRulesCTA'; @@ -19,18 +16,16 @@ import { RuleListErrors } from '../components/rules/RuleListErrors'; import { RuleListGroupView } from '../components/rules/RuleListGroupView'; import { RuleListStateView } from '../components/rules/RuleListStateView'; import { RuleStats } from '../components/rules/RuleStats'; -import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton'; import { shouldUsePrometheusRulesPrimary } from '../featureToggles'; -import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities'; import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces'; import { useFilteredRules, useRulesFilter } from '../hooks/useFilteredRules'; import { useUnifiedAlertingSelector } from '../hooks/useUnifiedAlertingSelector'; import { fetchAllPromAndRulerRulesAction, fetchAllPromRulesAction, fetchRulerRulesAction } from '../state/actions'; import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants'; import { GRAFANA_RULES_SOURCE_NAME, getAllRulesSourceNames } from '../utils/datasource'; -import { createRelativeUrl } from '../utils/url'; import { RuleListPageTitle } from './RuleListPageTitle'; +import { RuleListActionButtons } from './components/RuleListActionButtons'; const VIEWS = { groups: RuleListGroupView, @@ -127,13 +122,7 @@ const RuleListV1 = () => { navId="alert-list" isLoading={false} renderTitle={(title) => } - actions={ - hasAlertRulesCreated && ( - - - - ) - } + actions={} > @@ -162,48 +151,3 @@ const RuleListV1 = () => { }; export default RuleListV1; - -export function CreateAlertButton() { - const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule); - const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); - - const location = useLocation(); - - const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; - - const canCreateGrafanaRules = createRuleSupported && createRuleAllowed; - - if (canCreateGrafanaRules || canCreateCloudRules) { - return ( - - logInfo(LogMessages.alertRuleFromScratch)} - > - New alert rule - - {canCreateGrafanaRules && AIAlertRuleButtonComponent && } - - ); - } - return null; -} - -function ExportNewRuleButton() { - const returnTo = window.location.pathname + window.location.search; - const url = createRelativeUrl(`/alerting/export-new-rule`, { - returnTo, - }); - return ( - logInfo(LogMessages.exportNewGrafanaRule)} - > - Export rule definition - - ); -} diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx index 1f2f4c0b02e..9417ffa2230 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx @@ -155,11 +155,11 @@ describe('RuleList v2', () => { describe('RuleListActions', () => { const ui = { - newRuleButton: byRole('link', { name: /new alert rule/i }), + newRuleButton: byRole('link', { name: /^new alert rule$/i }), moreButton: byRole('button', { name: /more/i }), moreMenu: byRole('menu'), menuOptions: { - draftNewRule: byRole('link', { name: /draft a new rule/i }), + newAlertRuleForExport: byRole('link', { name: /new alert rule for export/i }), newGrafanaRecordingRule: byRole('link', { name: /new grafana recording rule/i }), newDataSourceRecordingRule: byRole('link', { name: /new data source recording rule/i }), }, @@ -191,7 +191,7 @@ describe('RuleListActions', () => { expect(ui.moreButton.get()).toBeInTheDocument(); }); - it('should only show Draft a new rule when the user has view Grafana rules permission', async () => { + it('should only show New alert rule for export when the user has view Grafana rules permission', async () => { grantUserPermissions([AccessControlAction.AlertingRuleRead]); const { user } = render(); @@ -200,7 +200,7 @@ describe('RuleListActions', () => { const menu = await ui.moreMenu.find(); expect(ui.newRuleButton.query()).not.toBeInTheDocument(); - expect(ui.menuOptions.draftNewRule.query(menu)).toBeInTheDocument(); + expect(ui.menuOptions.newAlertRuleForExport.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newGrafanaRecordingRule.query(menu)).not.toBeInTheDocument(); expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).not.toBeInTheDocument(); }); @@ -213,7 +213,7 @@ describe('RuleListActions', () => { await user.click(ui.moreButton.get()); const menu = await ui.moreMenu.find(); - expect(ui.menuOptions.draftNewRule.query(menu)).toBeInTheDocument(); + expect(ui.menuOptions.newAlertRuleForExport.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newGrafanaRecordingRule.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).not.toBeInTheDocument(); }); @@ -226,7 +226,7 @@ describe('RuleListActions', () => { await user.click(ui.moreButton.get()); const menu = await ui.moreMenu.find(); - expect(ui.menuOptions.draftNewRule.query(menu)).toBeInTheDocument(); + expect(ui.menuOptions.newAlertRuleForExport.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newGrafanaRecordingRule.query(menu)).not.toBeInTheDocument(); expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).toBeInTheDocument(); }); @@ -239,7 +239,7 @@ describe('RuleListActions', () => { await user.click(ui.moreButton.get()); const menu = await ui.moreMenu.find(); - expect(ui.menuOptions.draftNewRule.query(menu)).toBeInTheDocument(); + expect(ui.menuOptions.newAlertRuleForExport.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newGrafanaRecordingRule.query(menu)).toBeInTheDocument(); expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).toBeInTheDocument(); }); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx index 017750b45b1..a677cb1d248 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx @@ -47,7 +47,7 @@ export function RuleListActions() { diff --git a/public/app/features/alerting/unified/rule-list/components/RuleListActionButtons.tsx b/public/app/features/alerting/unified/rule-list/components/RuleListActionButtons.tsx new file mode 100644 index 00000000000..97da3d7430e --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/RuleListActionButtons.tsx @@ -0,0 +1,74 @@ +import { memo } from 'react'; + +import { Trans, t } from '@grafana/i18n'; +import { logInfo } from '@grafana/runtime'; +import { LinkButton, Stack } from '@grafana/ui'; + +import { LogMessages } from '../../Analytics'; +import { AIAlertRuleButtonComponent } from '../../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton'; +import { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities'; +import { createReturnTo } from '../../hooks/useReturnTo'; +import { createRelativeUrl } from '../../utils/url'; + +interface RuleListActionButtonsProps { + hasAlertRulesCreated: boolean; +} + +export const RuleListActionButtons = memo(({ hasAlertRulesCreated }) => { + if (!hasAlertRulesCreated) { + return null; + } + + return ( + + + + + ); +}); + +RuleListActionButtons.displayName = 'RuleListActionButtons'; + +function CreateAlertButtons() { + const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule); + const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule); + + const returnTo = createReturnTo(); + + const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed; + const canCreateGrafanaRules = createRuleSupported && createRuleAllowed; + + if (canCreateGrafanaRules || canCreateCloudRules) { + return ( + + {canCreateGrafanaRules && } + logInfo(LogMessages.alertRuleFromScratch)} + > + New alert rule + + + ); + } + return null; +} + +function ExportNewRuleButton() { + const returnTo = createReturnTo(); + const url = createRelativeUrl(`/alerting/export-new-rule`, { + returnTo, + }); + return ( + logInfo(LogMessages.exportNewGrafanaRule)} + > + Export rule definition + + ); +} diff --git a/public/app/features/auth-config/AuthProvidersListPage.tsx b/public/app/features/auth-config/AuthProvidersListPage.tsx index 37fdf7c5cfe..b299553d55c 100644 --- a/public/app/features/auth-config/AuthProvidersListPage.tsx +++ b/public/app/features/auth-config/AuthProvidersListPage.tsx @@ -4,7 +4,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { GrafanaEdition } from '@grafana/data/internal'; import { Trans } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; -import { Grid, TextLink, ToolbarButton } from '@grafana/ui'; +import { Alert, Grid, TextLink, ToolbarButton } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { config } from 'app/core/config'; import { StoreState } from 'app/types/store'; @@ -46,6 +46,13 @@ export const AuthConfigPageUnconnected = ({ }, [loadSettings]); const [showDrawer, setShowDrawer] = useState(false); + const [showSCIMBanner, setShowSCIMBanner] = useState(false); + + // Check if SCIM banner should be shown + useEffect(() => { + const isSCIMEnabled = config.featureToggles.enableSCIM || false; + setShowSCIMBanner(isSCIMEnabled); + }, []); const authProviders = getRegisteredAuthProviders(); const availableProviders = authProviders.filter((p) => !providerStatuses[p.id]?.hide); @@ -102,6 +109,14 @@ export const AuthConfigPageUnconnected = ({ } > + {showSCIMBanner && ( + setShowSCIMBanner(false)} style={{ marginBottom: 16 }}> + + SCIM is currently in development and not recommended for production use. Please use with caution and + expect potential changes. + + + )} {!providerList.length ? ( ) : ( diff --git a/public/app/features/auth-config/ErrorContainer.tsx b/public/app/features/auth-config/ErrorContainer.tsx index 336dde9716a..1a51263a364 100644 --- a/public/app/features/auth-config/ErrorContainer.tsx +++ b/public/app/features/auth-config/ErrorContainer.tsx @@ -25,12 +25,16 @@ export const ErrorContainerUnconnected = ({ error, warning, resetError, resetWar
{error && ( resetError()}> - {error.errors?.map((e, i) =>
{e}
)} + {error.errors?.map((e, i) => ( +
{e}
+ ))}
)} {warning && ( resetWarning()} severity="warning"> - {warning.errors?.map((e, i) =>
{e}
)} + {warning.errors?.map((e, i) => ( +
{e}
+ ))}
)}
diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx index a2bfe9ca651..4a2c9f4a82b 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx @@ -1,4 +1,4 @@ -import { render as rtlRender, screen } from '@testing-library/react'; +import { render as rtlRender, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HttpResponse, http } from 'msw'; import { setupServer, SetupServer } from 'msw/node'; @@ -120,6 +120,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => { canEditFolders: true, canViewPermissions: true, canSetPermissions: true, + canDeleteDashboards: true, }; beforeAll(() => { @@ -157,6 +158,17 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); afterEach(() => { + // Reset permissions back to defaults + Object.assign(mockPermissions, { + canCreateDashboards: true, + canEditDashboards: true, + canCreateFolders: true, + canDeleteFolders: true, + canEditFolders: true, + canViewPermissions: true, + canSetPermissions: true, + canDeleteDashboards: true, + }); jest.restoreAllMocks(); server.resetHandlers(); }); @@ -178,13 +190,8 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('does not show the "New" button if the user does not have permissions', async () => { - jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => { - return { - ...mockPermissions, - canCreateDashboards: false, - canCreateFolders: false, - }; - }); + mockPermissions.canCreateDashboards = false; + mockPermissions.canCreateFolders = false; render(); expect(await screen.findByRole('heading', { name: 'Dashboards' })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument(); @@ -281,13 +288,8 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('does not show the "New" button if the user does not have permissions', async () => { - jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => { - return { - ...mockPermissions, - canCreateDashboards: false, - canCreateFolders: false, - }; - }); + mockPermissions.canCreateDashboards = false; + mockPermissions.canCreateFolders = false; render(); expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'New' })).not.toBeInTheDocument(); @@ -299,15 +301,10 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('does not show the "Folder actions" button if the user does not have permissions', async () => { - jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => { - return { - ...mockPermissions, - canDeleteFolders: false, - canEditFolders: false, - canSetPermissions: false, - canViewPermissions: false, - }; - }); + mockPermissions.canDeleteFolders = false; + mockPermissions.canEditFolders = false; + mockPermissions.canSetPermissions = false; + mockPermissions.canViewPermissions = false; render(); expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Folder actions' })).not.toBeInTheDocument(); @@ -319,12 +316,7 @@ describe('browse-dashboards BrowseDashboardsPage', () => { }); it('does not show the "Edit title" button if the user does not have permissions', async () => { - jest.spyOn(permissions, 'getFolderPermissions').mockImplementation(() => { - return { - ...mockPermissions, - canEditFolders: false, - }; - }); + mockPermissions.canEditFolders = false; render(); expect(await screen.findByRole('heading', { name: folderA.item.title })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Edit title' })).not.toBeInTheDocument(); @@ -369,5 +361,60 @@ describe('browse-dashboards BrowseDashboardsPage', () => { expect(screen.getByRole('button', { name: 'Move' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); }); + + it('should not show checkbox for folder when user has dashboards:write but lacks folder edit permissions', async () => { + mockPermissions.canCreateFolders = false; + mockPermissions.canDeleteFolders = false; + mockPermissions.canEditFolders = false; + mockPermissions.canSetPermissions = false; + mockPermissions.canViewPermissions = false; + mockPermissions.canDeleteDashboards = false; + + render(); + + await waitFor(() => { + const checkbox = screen.queryByTestId( + selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid) + ); + + expect(checkbox).not.toBeInTheDocument(); + }); + }); + + it('should not show checkbox for folder when user has folder:write but lacks folder delete permissions', async () => { + mockPermissions.canCreateFolders = false; + mockPermissions.canDeleteFolders = false; + mockPermissions.canEditFolders = true; + mockPermissions.canSetPermissions = false; + mockPermissions.canViewPermissions = false; + + render(); + + await waitFor(() => { + const checkbox = screen.queryByTestId( + selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid) + ); + + expect(checkbox).not.toBeInTheDocument(); + }); + }); + + it('should show checkbox for folder when user has folder:write and folder delete permissions', async () => { + mockPermissions.canCreateFolders = false; + mockPermissions.canEditDashboards = false; + mockPermissions.canDeleteDashboards = false; + mockPermissions.canSetPermissions = false; + mockPermissions.canViewPermissions = false; + mockPermissions.canDeleteFolders = true; + mockPermissions.canEditFolders = true; + + render(); + + const checkbox = await screen.findByTestId( + selectors.pages.BrowseDashboards.table.checkbox(folderA_folderA.item.uid) + ); + + expect(checkbox).toBeInTheDocument(); + }); }); }); diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index f37973f7401..01e60815402 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -93,11 +93,23 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record { if (folderDTO) { const result = await saveFolder({ @@ -183,14 +195,14 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record isSearching ? ( ) : ( - + ) } diff --git a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx index bc2ffeb8caa..72c4663fa6d 100644 --- a/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderAlertingPage.test.tsx @@ -34,6 +34,7 @@ describe('browse-dashboards BrowseFolderAlertingPage', () => { canEditFolders: true, canViewPermissions: true, canSetPermissions: true, + canDeleteDashboards: true, }; beforeEach(() => { diff --git a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx index dec629c0181..c60a8a8de56 100644 --- a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx @@ -45,6 +45,7 @@ describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => { canEditFolders: true, canViewPermissions: true, canSetPermissions: true, + canDeleteDashboards: true, }; beforeAll(() => { diff --git a/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx b/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx index 3e18b71d399..b564740c716 100644 --- a/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx +++ b/public/app/features/browse-dashboards/RecentlyDeletedPage.tsx @@ -25,8 +25,8 @@ const RecentlyDeletedPage = memo(() => { const [searchState, stateManager] = useRecentlyDeletedStateManager(); const hasSelection = useHasSelection(); - const { canEditFolders, canEditDashboards } = getFolderPermissions(); - const canSelect = canEditFolders || canEditDashboards; + const { canEditFolders, canEditDashboards, canDeleteFolders, canDeleteDashboards } = getFolderPermissions(); + const permissions = { canEditFolders, canEditDashboards, canDeleteFolders, canDeleteDashboards }; useEffect(() => { stateManager.initStateFromUrl(undefined); @@ -75,7 +75,7 @@ const RecentlyDeletedPage = memo(() => { {({ width, height }) => ( setShowBulkDeleteProvisionedResource(false)} size="md" > - {/* TODO: Implement bulk delete for provisioned resources, PR will merge soon https://github.com/grafana/grafana/pull/107800 */} - - Bulk delete for provisioned resources is not implemented yet. - + { + setShowBulkDeleteProvisionedResource(false); + onActionComplete(); + }} + /> )} diff --git a/public/app/features/browse-dashboards/components/BrowseView.test.tsx b/public/app/features/browse-dashboards/components/BrowseView.test.tsx index 6cbabd17d6f..1a19d2b5b61 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.test.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.test.tsx @@ -42,9 +42,25 @@ jest.mock('app/features/browse-dashboards/api/services', () => { describe('browse-dashboards BrowseView', () => { const WIDTH = 800; const HEIGHT = 600; + const mockPermissions = { + canEditFolders: true, + canEditDashboards: true, + canDeleteFolders: true, + canDeleteDashboards: true, + }; + + afterEach(() => { + // Reset permissions back to defaults + Object.assign(mockPermissions, { + canEditFolders: true, + canEditDashboards: true, + canDeleteFolders: true, + canDeleteDashboards: true, + }); + }); it('expands and collapses a folder', async () => { - render(); + render(); await screen.findByText(folderA.item.title); await expandFolder(folderA.item); @@ -55,7 +71,7 @@ describe('browse-dashboards BrowseView', () => { }); it('checks items when selected', async () => { - render(); + render(); const checkbox = await screen.findByTestId(selectors.pages.BrowseDashboards.table.checkbox(dashbdD.item.uid)); expect(checkbox).not.toBeChecked(); @@ -65,7 +81,7 @@ describe('browse-dashboards BrowseView', () => { }); it('checks all descendants when a folder is selected', async () => { - render(); + render(); await screen.findByText(folderA.item.title); // First expand then click folderA @@ -82,7 +98,7 @@ describe('browse-dashboards BrowseView', () => { }); it('checks descendants loaded after a folder is selected', async () => { - render(); + render(); await screen.findByText(folderA.item.title); // First expand then click folderA @@ -102,7 +118,7 @@ describe('browse-dashboards BrowseView', () => { }); it('unchecks ancestors when unselecting an item', async () => { - render(); + render(); await screen.findByText(folderA.item.title); await expandFolder(folderA.item); @@ -126,7 +142,7 @@ describe('browse-dashboards BrowseView', () => { }); it('shows indeterminate checkboxes when a descendant is selected', async () => { - render(); + render(); await screen.findByText(folderA.item.title); await expandFolder(folderA.item); @@ -147,12 +163,21 @@ describe('browse-dashboards BrowseView', () => { describe('when there is no item in the folder', () => { it('shows a CTA for creating a dashboard if the user has editor rights', async () => { - render(); + render( + + ); expect(await screen.findByText('Create dashboard')).toBeInTheDocument(); }); it('shows a simple message if the user has viewer rights', async () => { - render(); + mockPermissions.canEditFolders = false; + mockPermissions.canEditDashboards = false; + mockPermissions.canDeleteFolders = false; + mockPermissions.canDeleteDashboards = false; + + render( + + ); expect(await screen.findByText('This folder is empty')).toBeInTheDocument(); }); }); diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index b20fa522fc7..5cd79f3341f 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -15,23 +15,25 @@ import { useLoadNextChildrenPage, } from '../state/hooks'; import { setFolderOpenState, setItemSelectionState, setAllSelection } from '../state/slice'; -import { BrowseDashboardsState, DashboardTreeSelection, SelectionState } from '../types'; +import { BrowseDashboardsState, DashboardTreeSelection, SelectionState, BrowseDashboardsPermissions } from '../types'; import { DashboardsTree } from './DashboardsTree'; +import { canSelectItems } from './utils'; interface BrowseViewProps { height: number; width: number; folderUID: string | undefined; - canSelect: boolean; + permissions: BrowseDashboardsPermissions; } -export function BrowseView({ folderUID, width, height, canSelect }: BrowseViewProps) { +export function BrowseView({ folderUID, width, height, permissions }: BrowseViewProps) { const status = useBrowseLoadingStatus(folderUID); const dispatch = useDispatch(); const flatTree = useFlatTreeState(folderUID); const selectedItems = useCheckboxSelectionState(); const childrenByParentUID = useChildrenByParentUIDState(); + const canSelect = canSelectItems(permissions); const handleFolderClick = useCallback( (clickedFolderUID: string, isOpen: boolean) => { @@ -156,7 +158,7 @@ export function BrowseView({ folderUID, width, height, canSelect }: BrowseViewPr return ( >, onDismissOverride?: () => void) => { + const defaultFailedItems: MoveResultFailed[] = [ + { + status: 'failed', + title: 'Dashboard 1', + errorMessage: 'Permission denied', + }, + { + status: 'failed', + title: 'Dashboard 2', + errorMessage: 'Network error', + }, + ]; + + const result = + resultOverrides !== undefined + ? resultOverrides.map((override) => ({ status: 'failed' as const, title: 'Default Title', ...override })) + : defaultFailedItems; + + const onDismiss = onDismissOverride || jest.fn(); + + const props = { + result, + onDismiss, + }; + + return { + user: userEvent.setup(), + ...render(), + props, + }; +}; + +describe('BulkActionFailureBanner', () => { + it('should display error alert with correct item count', () => { + const testData = [{ title: 'Single Item', errorMessage: 'Single error' }]; + setup(testData); + + // Test that an alert is rendered + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + + // Test structure: should have same number of list items as input data + const listItems = screen.getAllByRole('listitem'); + expect(listItems).toHaveLength(testData.length); + }); + + it('should render correct number of failed items with proper structure', () => { + const testData = [ + { title: 'Failed Dashboard A', errorMessage: 'Access denied' }, + { title: 'Failed Dashboard B', errorMessage: 'Validation failed' }, + ]; + setup(testData); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + + // Test structure: number of list items matches input + const listItems = screen.getAllByRole('listitem'); + expect(listItems).toHaveLength(testData.length); + + // Test that each item has the expected structure (title + error message) + listItems.forEach((item, index) => { + const title = testData[index].title; + const errorMessage = testData[index].errorMessage; + + if (title) { + expect(item).toHaveTextContent(title); + } + if (errorMessage) { + expect(item).toHaveTextContent(errorMessage); + } + }); + }); + + it('should handle mixed scenarios', () => { + const testData = [ + { title: 'Item without error' }, + { title: 'Item with empty error', errorMessage: '' }, + { title: 'Another item with error', errorMessage: 'Another error' }, + ]; + setup(testData); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + + const listItems = screen.getAllByRole('listitem'); + expect(listItems).toHaveLength(testData.length); + + // Test that items with error messages contain both title and error + // Items without errors should only contain title + testData.forEach((data, index) => { + const listItem = listItems[index]; + + // All items should have their title + if (data.title) { + expect(listItem).toHaveTextContent(data.title); + } + + // Only items with non-empty error messages should show the error + if (data.errorMessage && data.errorMessage.trim() !== '') { + expect(listItem).toHaveTextContent(data.errorMessage); + } + }); + }); + + it('should maintain list structure', () => { + const testData = [ + { title: 'Item 1', errorMessage: 'Error 1' }, + { title: 'Item 2', errorMessage: 'Error 2' }, + ]; + setup(testData); + + // Test semantic structure + const list = screen.getByRole('list'); + expect(list).toBeInTheDocument(); + + const listItems = screen.getAllByRole('listitem'); + expect(listItems).toHaveLength(testData.length); + }); + + it('should handle items without error messages', () => { + const testData = [{ title: 'Just Title Item' }]; + setup(testData); + + const listItems = screen.getAllByRole('listitem'); + expect(listItems).toHaveLength(1); + + const item = listItems[0]; + expect(item).toHaveTextContent(testData[0].title!); + // Should not contain colon separator when no error message + expect(item.textContent).not.toMatch(/:\s*.+$/); + }); + + it('should render dismissible alert', () => { + const onDismiss = jest.fn(); + setup([], onDismiss); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + + // Alert should have close button (dismissible) + const closeButton = screen.getByRole('button', { name: /close/i }); + expect(closeButton).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkActionFailureBanner.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkActionFailureBanner.tsx new file mode 100644 index 00000000000..332e0695144 --- /dev/null +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkActionFailureBanner.tsx @@ -0,0 +1,29 @@ +import { t } from '@grafana/i18n'; +import { Alert } from '@grafana/ui'; + +export type MoveResultFailed = { + status?: 'failed'; + title?: string; + errorMessage?: string; +}; + +export function BulkActionFailureBanner({ result, onDismiss }: { result: MoveResultFailed[]; onDismiss: () => void }) { + return ( + +
    + {result.map((item) => ( +
  • + {item.title} + {item.errorMessage && `: ${item.errorMessage}`} +
  • + ))} +
+
+ ); +} diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.test.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.test.tsx new file mode 100644 index 00000000000..e1dcbc41a94 --- /dev/null +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.test.tsx @@ -0,0 +1,62 @@ +import { render, screen } from '@testing-library/react'; + +import { BulkActionProgress, ProgressState } from './BulkActionProgress'; + +const setup = (progressOverrides: Partial = {}) => { + const defaultProgress: ProgressState = { + current: 5, + total: 10, + item: 'Test Dashboard', + ...progressOverrides, + }; + + const props = { + progress: defaultProgress, + }; + + return { + ...render(), + props, + }; +}; + +describe('BulkActionProgress', () => { + it('should render progress text with current and total values', () => { + setup({ current: 3, total: 8 }); + + expect(screen.getByText(/Progress: 3 of 8/)).toBeInTheDocument(); + }); + + it('should render current item being deleted', () => { + setup({ item: 'My Test Dashboard' }); + + expect(screen.getByText(/Deleting:/)).toBeInTheDocument(); + expect(screen.getByText(/My Test Dashboard/)).toBeInTheDocument(); + }); + + it('should handle edge case with total of 1', () => { + setup({ current: 1, total: 1 }); + + expect(screen.getByText(/Progress: 1 of 1/)).toBeInTheDocument(); + }); + + it('should handle edge case with zero current progress', () => { + setup({ current: 0, total: 5 }); + + expect(screen.getByText(/Progress: 0 of 5/)).toBeInTheDocument(); + }); + + it('should render all required elements together', () => { + setup({ current: 7, total: 15, item: 'Complex Dashboard Name' }); + + // Progress text + expect(screen.getByText(/Progress: 7 of 15/)).toBeInTheDocument(); + + // Spinner icon + expect(screen.getByTestId('Spinner')).toBeInTheDocument(); + + // Current item text + expect(screen.getByText(/Deleting:/)).toBeInTheDocument(); + expect(screen.getByText(/Complex Dashboard Name/)).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.tsx new file mode 100644 index 00000000000..fd8ac5196a8 --- /dev/null +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkActionProgress.tsx @@ -0,0 +1,32 @@ +import { Trans } from '@grafana/i18n'; +import { Box, Text, Stack, Spinner } from '@grafana/ui'; +import ProgressBar from 'app/features/provisioning/Shared/ProgressBar'; + +export interface ProgressState { + current: number; + total: number; + item: string; +} + +export function BulkActionProgress({ progress }: { progress: ProgressState }) { + const progressPercentage = Math.round((progress.current / progress.total) * 100); + + return ( + + + + + + + + + + {progress.item} + + + ); +} diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.test.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.test.tsx new file mode 100644 index 00000000000..045efed7e54 --- /dev/null +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.test.tsx @@ -0,0 +1,201 @@ +import { screen, waitFor } from '@testing-library/react'; +import { HttpResponse, http } from 'msw'; +import { render } from 'test/test-utils'; + +import { setBackendSrv } from '@grafana/runtime'; +import server, { setupMockServer } from '@grafana/test-utils/server'; +import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; +import { backendSrv } from 'app/core/services/backend_srv'; + +import { BulkDeleteProvisionedResource } from './BulkDeleteProvisionedResource'; + +// Set up backendSrv as recommended in the PR comment +setBackendSrv(backendSrv); +setupMockServer(); + +jest.mock('../utils', () => ({ + collectSelectedItems: jest.fn().mockReturnValue([ + { uid: 'folder-1', isFolder: true, displayName: 'Test Folder' }, + { uid: 'dashboard-1', isFolder: false, displayName: 'Test Dashboard' }, + ]), + fetchProvisionedDashboardPath: jest.fn().mockResolvedValue('/test/dashboard.json'), +})); + +jest.mock('../../state/hooks', () => ({ + useChildrenByParentUIDState: jest.fn().mockReturnValue({}), + rootItemsSelector: jest.fn().mockReturnValue({ + items: [ + { uid: 'folder-1', title: 'Test Folder', kind: 'folder' }, + { uid: 'dashboard-1', title: 'Test Dashboard', kind: 'dashboard' }, + ], + }), +})); + +jest.mock('../../state/utils', () => ({ + findItem: jest.fn().mockImplementation((rootItems: unknown[], childrenByUID: unknown, uid: string) => { + const mockRootItems = [ + { uid: 'folder-1', title: 'Test Folder', kind: 'folder' }, + { uid: 'dashboard-1', title: 'Test Dashboard', kind: 'dashboard' }, + ]; + return mockRootItems.find((item) => item.uid === uid); + }), +})); + +jest.mock('../BrowseActions/DescendantCount', () => ({ + DescendantCount: jest.fn(({ selectedItems }) => ( +
+ Mocked descendant count for {Object.keys(selectedItems.folder).length} folders and{' '} + {Object.keys(selectedItems.dashboard).length} dashboards +
+ )), +})); + +jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => ({ + useGetResourceRepositoryView: jest.fn(), +})); + +describe('BulkDeleteProvisionedResource', () => { + const defaultRepository: RepositoryView = { + name: 'test-folder', // This must match the folderUid passed to the component + type: 'github', + title: 'Test Repository', + target: 'folder', + workflows: ['branch', 'write'], + }; + + const selectedItems = { + folder: { 'folder-1': true }, + dashboard: { 'dashboard-1': true }, + }; + + beforeEach(() => { + server.use( + http.delete('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/:name/files/*', () => { + return HttpResponse.json({ + urls: { repositoryURL: 'https://github.com/test/repo' }, + }); + }) + ); + jest.clearAllMocks(); + + const { useGetResourceRepositoryView } = jest.requireMock( + 'app/features/provisioning/hooks/useGetResourceRepositoryView' + ); + useGetResourceRepositoryView.mockReturnValue({ + repository: defaultRepository, + folder: { + metadata: { + annotations: { + 'grafana.app/file-path': '/test/folder', + }, + }, + }, + isInstanceManaged: false, + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + function setup(repository: RepositoryView | null = defaultRepository) { + const onDismiss = jest.fn(); + + const { useGetResourceRepositoryView } = jest.requireMock( + 'app/features/provisioning/hooks/useGetResourceRepositoryView' + ); + useGetResourceRepositoryView.mockReturnValue({ + repository, + folder: repository + ? { + metadata: { + annotations: { + 'grafana.app/file-path': '/test/folder', + }, + }, + } + : null, + isInstanceManaged: false, + }); + + const renderResult = render( + + ); + + return { + onDismiss, + ...renderResult, + }; + } + + it('renders the delete warning and form', async () => { + setup(); + + expect(await screen.findByText(/This will delete selected folders and their descendants/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Delete/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Cancel/i })).toBeInTheDocument(); + }); + + it('calls onDismiss when Cancel is clicked', async () => { + const { onDismiss, user } = setup(); + + await user.click(screen.getByRole('button', { name: /Cancel/i })); + + expect(onDismiss).toHaveBeenCalled(); + }); + + it('handles successful deletion', async () => { + const { user } = setup(); + + await user.click(screen.getByRole('button', { name: /Delete/i })); + + expect(await screen.findByText(/All resources have been deleted successfully/)).toBeInTheDocument(); + }); + + it('handles deletion errors', async () => { + const { user } = setup(); + + // Mock API to return error for this test + server.use( + http.delete('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/:name/files/*', () => { + return HttpResponse.json({ message: 'Network error' }, { status: 500 }); + }) + ); + + await user.click(screen.getByRole('button', { name: /Delete/i })); + + await waitFor(() => { + // Should show error alert with failed items + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByLabelText(/items failed/)).toBeInTheDocument(); + + // Should have error list items for both folder and dashboard + const errorItems = screen.getAllByRole('listitem'); + expect(errorItems).toHaveLength(2); // One for folder, one for dashboard + }); + }); + + it('shows loading state during deletion', async () => { + const { user } = setup(); + + // Mock slow API response + server.use( + http.delete('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/:name/files/*', async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + return HttpResponse.json({ + urls: { repositoryURL: 'https://github.com/test/repo' }, + }); + }) + ); + + await user.click(screen.getByRole('button', { name: /Delete/i })); + + expect(screen.getByText(/Deleting.../)).toBeInTheDocument(); + }); + + it('returns null when repository is not available', () => { + const { container } = setup(null); + + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.tsx new file mode 100644 index 00000000000..d5fd35ff1bd --- /dev/null +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkDeleteProvisionedResource.tsx @@ -0,0 +1,287 @@ +import { useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom-v5-compat'; + +import { Trans, t } from '@grafana/i18n'; +import { Alert, Box, Button, Stack } from '@grafana/ui'; +import { + DeleteRepositoryFilesWithPathApiArg, + DeleteRepositoryFilesWithPathApiResponse, + RepositoryView, + useDeleteRepositoryFilesWithPathMutation, +} from 'app/api/clients/provisioning/v0alpha1'; +import { extractErrorMessage } from 'app/api/utils'; +import { AnnoKeySourcePath } from 'app/features/apiserver/types'; +import { ResourceEditFormSharedFields } from 'app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields'; +import { getDefaultWorkflow, getWorkflowOptions } from 'app/features/dashboard-scene/saving/provisioned/defaults'; +import { generateTimestamp } from 'app/features/dashboard-scene/saving/provisioned/utils/timestamp'; +import { buildResourceBranchRedirectUrl } from 'app/features/dashboard-scene/settings/utils'; +import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView'; +import { WorkflowOption } from 'app/features/provisioning/types'; +import { useSelector } from 'app/types/store'; + +import { useChildrenByParentUIDState, rootItemsSelector } from '../../state/hooks'; +import { findItem } from '../../state/utils'; +import { DashboardTreeSelection } from '../../types'; +import { DescendantCount } from '../BrowseActions/DescendantCount'; +import { collectSelectedItems, fetchProvisionedDashboardPath } from '../utils'; + +import { BulkActionFailureBanner, MoveResultFailed } from './BulkActionFailureBanner'; +import { BulkActionProgress, ProgressState } from './BulkActionProgress'; + +interface BulkDeleteFormData { + comment: string; + ref: string; + workflow?: WorkflowOption; +} + +interface FormProps extends BulkDeleteProvisionResourceProps { + initialValues: BulkDeleteFormData; + repository: RepositoryView; + workflowOptions: Array<{ label: string; value: string }>; + folderPath?: string; +} + +interface BulkDeleteProvisionResourceProps { + folderUid?: string; + selectedItems: Omit; + onDismiss?: () => void; +} + +type BulkSuccessResponse = Array<{ + index: number; + item: DeleteRepositoryFilesWithPathApiArg; + data: DeleteRepositoryFilesWithPathApiResponse; +}>; + +type MoveResultSuccessState = { + allSuccess: boolean; + repoUrl?: string; +}; + +function FormContent({ initialValues, selectedItems, repository, workflowOptions, folderPath, onDismiss }: FormProps) { + // States + const [progress, setProgress] = useState(null); + const [failureResults, setFailureResults] = useState(); + const [successState, setSuccessState] = useState({ + allSuccess: false, + repoUrl: '', + }); + const [hasSubmitted, setHasSubmitted] = useState(false); + + // Hooks + const [deleteRepoFile, request] = useDeleteRepositoryFilesWithPathMutation(); + const methods = useForm({ defaultValues: initialValues }); + const childrenByParentUID = useChildrenByParentUIDState(); + const rootItems = useSelector(rootItemsSelector); + const { handleSubmit, watch } = methods; + const workflow = watch('workflow'); + const navigate = useNavigate(); + + const getResourcePath = async (uid: string, isFolder: boolean): Promise => { + const item = findItem(rootItems?.items || [], childrenByParentUID, uid); + if (!item) { + return undefined; + } + return isFolder ? `${folderPath}/${item.title}/` : fetchProvisionedDashboardPath(uid); + }; + + const handleSuccess = () => { + if (workflow === 'branch') { + onDismiss?.(); + if (successState.repoUrl) { + const url = buildResourceBranchRedirectUrl({ + paramName: 'repo_url', + paramValue: successState.repoUrl, + repoType: repository.type, + }); + + navigate(url); + return; + } + window.location.reload(); + } else { + onDismiss?.(); + window.location.reload(); + } + }; + + const handleSubmitForm = async (data: BulkDeleteFormData) => { + setFailureResults(undefined); + setHasSubmitted(true); + + const targets = collectSelectedItems(selectedItems, childrenByParentUID, rootItems?.items || []); + + if (targets.length > 0) { + setProgress({ + current: 0, + total: targets.length, + item: targets[0].displayName || 'Unknown', + }); + } + + const successes: BulkSuccessResponse = []; + const failures: MoveResultFailed[] = []; + + // Iterate through each selected item and delete it + // We want sequential processing to avoid overwhelming the API + for (let i = 0; i < targets.length; i++) { + const { uid, isFolder, displayName } = targets[i]; + setProgress({ + current: i, + total: targets.length, + item: displayName, + }); + + try { + // get path in repository + const path = await getResourcePath(uid, isFolder); + if (!path) { + failures.push({ + status: 'failed', + title: `${isFolder ? 'Folder' : 'Dashboard'}: ${displayName}`, + errorMessage: t('browse-dashboards.bulk-delete-resources-form.error-path-not-found', 'Path not found'), + }); + continue; + } + + // build params + const deleteParams: DeleteRepositoryFilesWithPathApiArg = { + name: repository.name, + path, + ref: workflow === 'write' ? undefined : data.ref, + message: data.comment || `Delete resource ${path}`, + }; + + // perform delete operation + const response = await deleteRepoFile(deleteParams).unwrap(); + successes.push({ index: i, item: deleteParams, data: response }); + } catch (error: unknown) { + failures.push({ + status: 'failed', + title: `${isFolder ? 'Folder' : 'Dashboard'}: ${displayName}`, + errorMessage: extractErrorMessage(error), + }); + } + + setProgress({ + current: i + 1, + total: targets.length, + item: targets[i + 1]?.displayName, + }); + } + + setProgress(null); + + if (successes.length > 0 && failures.length === 0) { + // handleSuccess(successes); + setSuccessState({ + allSuccess: true, + repoUrl: successes[0].data.urls?.repositoryURL, + }); + } else if (failures.length > 0) { + setFailureResults(failures); + } + }; + + const getPostSubmitContent = () => { + if (progress) { + return ; + } + + if (successState.allSuccess) { + return ( + <> + + + All resources have been deleted successfully. + + + + + + + ); + } else if (failureResults) { + return setFailureResults(undefined)} />; + } + + return null; + }; + + return ( + +
+ + + + This will delete selected folders and their descendants. In total, this will affect: + + + + + {hasSubmitted ? ( + getPostSubmitContent() + ) : ( + <> + + + + + + + + )} + +
+
+ ); +} + +export function BulkDeleteProvisionedResource({ + folderUid, + selectedItems, + onDismiss, +}: BulkDeleteProvisionResourceProps) { + const { repository, folder } = useGetResourceRepositoryView({ folderName: folderUid }); + + const workflowOptions = getWorkflowOptions(repository); + const folderPath = folder?.metadata?.annotations?.[AnnoKeySourcePath] || ''; + const timestamp = generateTimestamp(); + + const initialValues = { + comment: '', + ref: `bulk-delete/${timestamp}`, + workflow: getDefaultWorkflow(repository), + }; + + if (!repository) { + return null; + } + + return ( + + ); +} diff --git a/public/app/features/browse-dashboards/components/CheckboxCell.tsx b/public/app/features/browse-dashboards/components/CheckboxCell.tsx index c5b9e7f3d51..60995129f90 100644 --- a/public/app/features/browse-dashboards/components/CheckboxCell.tsx +++ b/public/app/features/browse-dashboards/components/CheckboxCell.tsx @@ -7,12 +7,13 @@ import { Checkbox, useStyles2 } from '@grafana/ui'; import { DashboardsTreeCellProps, SelectionState } from '../types'; -import { isSharedWithMe } from './utils'; +import { isSharedWithMe, canEditItemType } from './utils'; export default function CheckboxCell({ row: { original: row }, isSelected, onItemSelectionChange, + permissions, }: DashboardsTreeCellProps) { const item = row.item; @@ -32,6 +33,11 @@ export default function CheckboxCell({ return ; } + // Check if user can edit this specific item type + if (permissions && !canEditItemType(item.kind, permissions)) { + return ; + } + const state = isSelected(item); return ( diff --git a/public/app/features/browse-dashboards/components/DashboardsTree.test.tsx b/public/app/features/browse-dashboards/components/DashboardsTree.test.tsx index bc2bebe5cce..e8109397870 100644 --- a/public/app/features/browse-dashboards/components/DashboardsTree.test.tsx +++ b/public/app/features/browse-dashboards/components/DashboardsTree.test.tsx @@ -23,6 +23,12 @@ function render(...[ui, options]: Parameters) { describe('browse-dashboards DashboardsTree', () => { const WIDTH = 800; const HEIGHT = 600; + const mockPermissions = { + canEditFolders: true, + canEditDashboards: true, + canDeleteFolders: true, + canDeleteDashboards: true, + }; const folder = wellFormedFolder(1); const emptyFolderIndicator = wellFormedEmptyFolder(); @@ -36,10 +42,20 @@ describe('browse-dashboards DashboardsTree', () => { config.sharedWithMeFolderUID = 'sharedwithme'; }); + afterEach(() => { + // Reset permissions back to defaults + Object.assign(mockPermissions, { + canEditFolders: true, + canEditDashboards: true, + canDeleteFolders: true, + canDeleteDashboards: true, + }); + }); + it('renders a dashboard item', () => { render( { }); it('does not render checkbox when disabled', () => { + mockPermissions.canEditFolders = false; + mockPermissions.canEditDashboards = false; + mockPermissions.canDeleteFolders = false; + mockPermissions.canDeleteDashboards = false; + render( { it('renders a folder item', () => { render( { it('renders a folder link', () => { render( { render( { render( { const handler = jest.fn(); render( { it('renders empty folder indicators', () => { render( SelectionState; onFolderClick: (uid: string, newOpenState: boolean) => void; onAllSelectionChange: (newState: boolean) => void; @@ -48,7 +54,7 @@ export function DashboardsTree({ onItemSelectionChange, isItemLoaded, requestLoadMore, - canSelect = false, + permissions, }: DashboardsTreeProps) { const treeID = useId(); @@ -94,10 +100,11 @@ export function DashboardsTree({ Header: t('browse-dashboards.dashboards-tree.tags-column', 'Tags'), Cell: TagsCell, }; + const canSelect = canSelectItems(permissions); const columns = [canSelect && checkboxColumn, nameColumn, tagsColumns].filter(isTruthy); return columns; - }, [onFolderClick, canSelect]); + }, [onFolderClick, permissions]); const table = useTable({ columns: tableColumns, data: items }, useCustomFlexLayout); const { getTableProps, getTableBodyProps, headerGroups } = table; @@ -109,10 +116,11 @@ export function DashboardsTree({ onAllSelectionChange, onItemSelectionChange, treeID, + permissions, }), // we need this to rerender if items changes // eslint-disable-next-line react-hooks/exhaustive-deps - [table, isSelected, onAllSelectionChange, onItemSelectionChange, items, treeID] + [table, isSelected, onAllSelectionChange, onItemSelectionChange, items, treeID, permissions] ); const handleIsItemLoaded = useCallback( @@ -156,7 +164,7 @@ export function DashboardsTree({ return (
- {column.render('Header', { isSelected, onAllSelectionChange })} + {column.render('Header', { isSelected, onAllSelectionChange, permissions })}
); })} @@ -203,12 +211,13 @@ interface VirtualListRowProps { onAllSelectionChange: DashboardsTreeCellProps['onAllSelectionChange']; onItemSelectionChange: DashboardsTreeCellProps['onItemSelectionChange']; treeID: string; + permissions: BrowseDashboardsPermissions; }; } function VirtualListRow({ index, style, data }: VirtualListRowProps) { const styles = useStyles2(getStyles); - const { table, isSelected, onItemSelectionChange, treeID } = data; + const { table, isSelected, onItemSelectionChange, treeID, permissions } = data; const { rows, prepareRow } = table; const row = rows[index]; @@ -240,7 +249,7 @@ function VirtualListRow({ index, style, data }: VirtualListRowProps) { return (
- {cell.render('Cell', { isSelected, onItemSelectionChange, treeID })} + {cell.render('Cell', { isSelected, onItemSelectionChange, treeID, permissions })}
); })} diff --git a/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.test.tsx b/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.test.tsx index 01d9216ff5f..00687677e94 100644 --- a/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.test.tsx +++ b/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.test.tsx @@ -30,6 +30,13 @@ jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ }, }, }, + provisioningAPIv0alpha1: { + endpoints: { + listRepository: { + select: jest.fn(() => () => ({ data: { items: [] } })), + }, + }, + }, })); jest.mock('../hooks/useProvisionedFolderFormData'); @@ -104,7 +111,6 @@ const defaultHookData: ProvisionedFolderFormDataResult = { { label: 'Write directly', value: 'write' }, { label: 'Create branch', value: 'branch' }, ], - isGitHub: true, repository: mockRepository, folder: mockFolder, initialValues: mockFormData, @@ -291,9 +297,11 @@ describe('DeleteProvisionedFolderForm', () => { const { mockNavigate } = setup({}, { ...defaultHookData, initialValues: branchFormData }, successState); await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith( - '/dashboards?new_pull_request_url=https://github.com/test/repo/pull/new' - ); + const expectedParams = new URLSearchParams(); + expectedParams.set('new_pull_request_url', 'https://github.com/test/repo/pull/new'); + const expectedUrl = `/dashboards?${expectedParams.toString()}`; + + expect(mockNavigate).toHaveBeenCalledWith(expectedUrl); }); }); }); diff --git a/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.tsx b/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.tsx index 2491b7bc506..70638377a3a 100644 --- a/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.tsx +++ b/public/app/features/browse-dashboards/components/DeleteProvisionedFolderForm.tsx @@ -11,6 +11,7 @@ import { RepositoryView, useDeleteRepositoryFilesWithPathMutation } from 'app/ap import { AnnoKeySourcePath } from 'app/features/apiserver/types'; import { ResourceEditFormSharedFields } from 'app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields'; import { BaseProvisionedFormData } from 'app/features/dashboard-scene/saving/shared'; +import { buildResourceBranchRedirectUrl } from 'app/features/dashboard-scene/settings/utils'; import { FolderDTO } from 'app/types/folders'; import { useProvisionedFolderFormData } from '../hooks/useProvisionedFolderFormData'; @@ -23,7 +24,6 @@ interface FormProps extends DeleteProvisionedFolderFormProps { repository?: RepositoryView; workflowOptions: Array<{ label: string; value: string }>; folder?: Folder; - isGitHub: boolean; } interface DeleteProvisionedFolderFormProps { @@ -31,15 +31,7 @@ interface DeleteProvisionedFolderFormProps { onDismiss?: () => void; } -function FormContent({ - initialValues, - parentFolder, - repository, - workflowOptions, - folder, - isGitHub, - onDismiss, -}: FormProps) { +function FormContent({ initialValues, parentFolder, repository, workflowOptions, folder, onDismiss }: FormProps) { const resourceId = parentFolder?.uid || ''; const [deleteRepoFile, request] = useDeleteRepositoryFilesWithPathMutation(); @@ -70,7 +62,12 @@ function FormContent({ if (request.isSuccess && repository) { const prUrl = request.data?.urls?.newPullRequestURL; if (workflow === 'branch' && prUrl) { - navigate(`/dashboards?new_pull_request_url=${prUrl}`); + const url = buildResourceBranchRedirectUrl({ + paramName: 'new_pull_request_url', + paramValue: prUrl, + repoType: request.data?.repository?.type, + }); + navigate(url); return; } @@ -128,7 +125,7 @@ function FormContent({ isNew={false} workflow={workflow} workflowOptions={workflowOptions} - isGitHub={isGitHub} + repository={repository} /> {/* Delete / Cancel button */} @@ -149,7 +146,7 @@ function FormContent({ } export function DeleteProvisionedFolderForm({ parentFolder, onDismiss }: DeleteProvisionedFolderFormProps) { - const { workflowOptions, isGitHub, repository, folder, initialValues } = useProvisionedFolderFormData({ + const { workflowOptions, repository, folder, initialValues } = useProvisionedFolderFormData({ folderUid: parentFolder?.uid, action: 'delete', title: parentFolder?.title, @@ -167,7 +164,6 @@ export function DeleteProvisionedFolderForm({ parentFolder, onDismiss }: DeleteP repository={repository} workflowOptions={workflowOptions} folder={folder} - isGitHub={isGitHub} /> ); } diff --git a/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx b/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx index 535be61f806..dd019520aeb 100644 --- a/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx +++ b/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx @@ -32,6 +32,7 @@ describe('browse-dashboards FolderActionsButton', () => { canEditFolders: true, canViewPermissions: true, canSetPermissions: true, + canDeleteDashboards: true, }; beforeEach(() => { diff --git a/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.test.tsx b/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.test.tsx index a983a66ada0..2242a8965ae 100644 --- a/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.test.tsx +++ b/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.test.tsx @@ -37,6 +37,13 @@ jest.mock('app/features/manage-dashboards/services/ValidationSrv', () => { jest.mock('app/api/clients/provisioning/v0alpha1', () => { return { useCreateRepositoryFilesWithPathMutation: jest.fn(), + provisioningAPIv0alpha1: { + endpoints: { + listRepository: { + select: jest.fn().mockReturnValue(() => ({ data: { items: [] } })), + }, + }, + }, }; }); @@ -139,7 +146,6 @@ const mockHookData: ProvisionedFolderFormDataResult = { { label: 'Commit directly', value: 'write' }, { label: 'Create a branch', value: 'branch' }, ], - isGitHub: true, initialValues: { title: '', comment: '', diff --git a/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.tsx b/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.tsx index f7f141fac01..a453490e07c 100644 --- a/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.tsx +++ b/public/app/features/browse-dashboards/components/NewProvisionedFolderForm.tsx @@ -12,6 +12,7 @@ import { RepositoryView, useCreateRepositoryFilesWithPathMutation } from 'app/ap import { AnnoKeySourcePath, Resource } from 'app/features/apiserver/types'; import { ResourceEditFormSharedFields } from 'app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields'; import { BaseProvisionedFormData } from 'app/features/dashboard-scene/saving/shared'; +import { buildResourceBranchRedirectUrl } from 'app/features/dashboard-scene/settings/utils'; import { PROVISIONING_URL } from 'app/features/provisioning/constants'; import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam'; import { FolderDTO } from 'app/types/folders'; @@ -26,14 +27,13 @@ interface FormProps extends Props { repository?: RepositoryView; workflowOptions: Array<{ label: string; value: string }>; folder?: Folder; - isGitHub: boolean; } interface Props { parentFolder?: FolderDTO; onDismiss?: () => void; } -function FormContent({ initialValues, repository, workflowOptions, folder, isGitHub, onDismiss }: FormProps) { +function FormContent({ initialValues, repository, workflowOptions, folder, onDismiss }: FormProps) { const { prURL } = usePullRequestParam(); const navigate = useNavigate(); const [create, request] = useCreateRepositoryFilesWithPathMutation(); @@ -62,6 +62,17 @@ function FormContent({ initialValues, repository, workflowOptions, folder, isGit ], }); + const prUrl = request.data?.urls?.newPullRequestURL; + if (workflow === 'branch' && prUrl) { + const url = buildResourceBranchRedirectUrl({ + paramName: 'new_pull_request_url', + paramValue: prUrl, + repoType: request.data?.repository?.type, + }); + navigate(url); + return; + } + // TODO: Update when the upsert type is fixed // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const folder = request.data.resource?.upsert as Resource; @@ -159,7 +170,7 @@ function FormContent({ initialValues, repository, workflowOptions, folder, isGit isNew={false} workflow={workflow} workflowOptions={workflowOptions} - isGitHub={isGitHub} + repository={repository} hidePath /> @@ -197,7 +208,7 @@ function FormContent({ initialValues, repository, workflowOptions, folder, isGit } export function NewProvisionedFolderForm({ parentFolder, onDismiss }: Props) { - const { workflowOptions, isGitHub, repository, folder, initialValues } = useProvisionedFolderFormData({ + const { workflowOptions, repository, folder, initialValues } = useProvisionedFolderFormData({ folderUid: parentFolder?.uid, action: 'create', title: '', // Empty title for new folders @@ -215,7 +226,6 @@ export function NewProvisionedFolderForm({ parentFolder, onDismiss }: Props) { repository={repository} workflowOptions={workflowOptions} folder={folder} - isGitHub={isGitHub} /> ); } diff --git a/public/app/features/browse-dashboards/components/ProvisionedFolderPreviewBanner.tsx b/public/app/features/browse-dashboards/components/ProvisionedFolderPreviewBanner.tsx index 32068d49077..f4b124357bc 100644 --- a/public/app/features/browse-dashboards/components/ProvisionedFolderPreviewBanner.tsx +++ b/public/app/features/browse-dashboards/components/ProvisionedFolderPreviewBanner.tsx @@ -5,7 +5,7 @@ import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequ export function ProvisionedFolderPreviewBanner({ queryParams }: CommonBannerProps) { const provisioningEnabled = config.featureToggles.provisioning; - const { prURL, newPrURL } = usePullRequestParam(); + const { prURL, newPrURL, repoURL } = usePullRequestParam(); if (!provisioningEnabled || 'kiosk' in queryParams) { return null; @@ -19,5 +19,9 @@ export function ProvisionedFolderPreviewBanner({ queryParams }: CommonBannerProp return ; } + if (repoURL) { + return ; + } + return null; } diff --git a/public/app/features/browse-dashboards/components/SearchView.tsx b/public/app/features/browse-dashboards/components/SearchView.tsx index 281455f140e..7f7b1c9cbb3 100644 --- a/public/app/features/browse-dashboards/components/SearchView.tsx +++ b/public/app/features/browse-dashboards/components/SearchView.tsx @@ -11,11 +11,14 @@ import { useDispatch, useSelector } from 'app/types/store'; import { useHasSelection } from '../state/hooks'; import { setAllSelection, setItemSelectionState } from '../state/slice'; +import { BrowseDashboardsPermissions } from '../types'; + +import { canEditItemType, canSelectItems } from './utils'; interface SearchViewProps { height: number; width: number; - canSelect: boolean; + permissions: BrowseDashboardsPermissions; searchState: SearchState; searchStateManager: SearchStateManager; emptyState?: ReactNode; @@ -48,7 +51,7 @@ const initialLoadingView = { export function SearchView({ width, height, - canSelect, + permissions, searchState, searchStateManager: stateManager, emptyState: emptyStateProp, @@ -67,7 +70,12 @@ export function SearchView({ return false; } - // Currently, this indicates _some_ items are selected, not nessicarily all are + // Check if user has permission to select this item type + if (!canEditItemType(kind, permissions)) { + return false; + } + + // Currently, this indicates _some_ items are selected, not necessarily all are // selected. if (kind === '*' && uid === '*') { return hasSelection; @@ -78,7 +86,7 @@ export function SearchView({ return selectedItems[assertDashboardViewItemKind(kind)][uid] ?? false; }, - [selectedItems, hasSelection] + [selectedItems, hasSelection, permissions] ); const clearSelection = useCallback(() => { @@ -87,13 +95,17 @@ export function SearchView({ const handleItemSelectionChange = useCallback( (kind: string, uid: string) => { + if (!canEditItemType(kind, permissions)) { + return; // Cannot select this item + } + const newIsSelected = !selectionChecker(kind, uid); dispatch( setItemSelectionState({ item: { kind: assertDashboardViewItemKind(kind), uid }, isSelected: newIsSelected }) ); }, - [selectionChecker, dispatch] + [selectionChecker, dispatch, permissions] ); if (value.totalRows === 0) { @@ -113,6 +125,7 @@ export function SearchView({ return
{emptyState}
; } + const canSelect = canSelectItems(permissions); const props: SearchResultsProps = { response: value, selection: canSelect ? selectionChecker : undefined, diff --git a/public/app/features/browse-dashboards/components/utils.ts b/public/app/features/browse-dashboards/components/utils.ts index 8f29702bda7..66a85a6d0ee 100644 --- a/public/app/features/browse-dashboards/components/utils.ts +++ b/public/app/features/browse-dashboards/components/utils.ts @@ -1,7 +1,12 @@ import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; +import { AnnoKeySourcePath } from 'app/features/apiserver/types'; +import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; +import { DashboardViewItem } from 'app/features/search/types'; -import { DashboardViewItemWithUIItems } from '../types'; +import { useChildrenByParentUIDState } from '../state/hooks'; +import { findItem } from '../state/utils'; +import { DashboardTreeSelection, DashboardViewItemWithUIItems, BrowseDashboardsPermissions } from '../types'; export function makeRowID(baseId: string, item: DashboardViewItemWithUIItems) { return baseId + item.uid; @@ -57,3 +62,61 @@ export function formatFolderName(folderName?: string): string { return result; } + +// Fetch provisioned dashboard path in repository +export async function fetchProvisionedDashboardPath(uid: string): Promise { + try { + const dto = await getDashboardAPI().getDashboardDTO(uid); + const sourcePath = + 'meta' in dto + ? dto.meta.k8s?.annotations?.[AnnoKeySourcePath] || dto.meta.provisionedExternalId + : dto.metadata?.annotations?.[AnnoKeySourcePath]; + return `${sourcePath}`; + } catch (error) { + console.error('Error fetching provisioned dashboard path:', error); + return undefined; + } +} + +// Collect selected dashboard and folder from the DashboardTreeSelection +// This is used to prepare the items for bulk delete operation. +export function collectSelectedItems( + selectedItems: Omit, + childrenByParentUID: ReturnType, + rootItems: DashboardViewItem[] = [] +) { + const targets: Array<{ uid: string; isFolder: boolean; displayName: string }> = []; + + // folders + for (const [uid, selected] of Object.entries(selectedItems.folder)) { + if (selected) { + const item = findItem(rootItems, childrenByParentUID, uid); + targets.push({ uid, isFolder: true, displayName: item?.title || uid }); + } + } + + // dashboards + for (const [uid, selected] of Object.entries(selectedItems.dashboard)) { + if (selected) { + const item = findItem(rootItems, childrenByParentUID, uid); + targets.push({ uid, isFolder: false, displayName: item?.title || uid }); + } + } + + return targets; +} + +export function canEditItemType(itemKind: string, permissions: BrowseDashboardsPermissions) { + const { canEditFolders, canDeleteFolders, canEditDashboards, canDeleteDashboards } = permissions; + return itemKind === 'folder' + ? Boolean(canEditFolders || canDeleteFolders) + : Boolean(canEditDashboards || canDeleteDashboards); +} + +export function canSelectItems(permissions: BrowseDashboardsPermissions) { + const { canEditFolders, canDeleteFolders, canEditDashboards, canDeleteDashboards } = permissions; + // Users can select items only if they have both edit and delete permissions for at least one item type + const canSelectFolders = canEditFolders || canDeleteFolders; + const canSelectDashboards = canEditDashboards || canDeleteDashboards; + return Boolean(canSelectFolders || canSelectDashboards); +} diff --git a/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts b/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts index 6fbc5f6c6eb..8ea8c4835f0 100644 --- a/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts +++ b/public/app/features/browse-dashboards/hooks/useProvisionedFolderFormData.ts @@ -19,7 +19,6 @@ export interface ProvisionedFolderFormDataResult { repository?: RepositoryView; folder?: Folder; workflowOptions: Array<{ label: string; value: string }>; - isGitHub: boolean; initialValues?: BaseProvisionedFormData; } @@ -34,7 +33,6 @@ export function useProvisionedFolderFormData({ const { repository, folder, isLoading } = useGetResourceRepositoryView({ folderName: folderUid }); const workflowOptions = getWorkflowOptions(repository); - const isGitHub = repository?.type === 'github'; const timestamp = generateTimestamp(); const initialValues = useMemo(() => { @@ -57,7 +55,6 @@ export function useProvisionedFolderFormData({ repository, folder, workflowOptions, - isGitHub, initialValues, }; } diff --git a/public/app/features/browse-dashboards/permissions.ts b/public/app/features/browse-dashboards/permissions.ts index d589af9480a..ff7839bf2de 100644 --- a/public/app/features/browse-dashboards/permissions.ts +++ b/public/app/features/browse-dashboards/permissions.ts @@ -20,6 +20,7 @@ export function getFolderPermissions(folderDTO?: FolderDTO) { const canCreateDashboards = checkFolderPermission(AccessControlAction.DashboardsCreate, folderDTO); const canCreateFolders = checkCanCreateFolders(folderDTO); const canDeleteFolders = checkFolderPermission(AccessControlAction.FoldersDelete, folderDTO); + const canDeleteDashboards = checkFolderPermission(AccessControlAction.DashboardsDelete, folderDTO); const canEditDashboards = checkFolderPermission(AccessControlAction.DashboardsWrite, folderDTO); const canEditFolders = checkFolderPermission(AccessControlAction.FoldersWrite, folderDTO); const canSetPermissions = checkFolderPermission(AccessControlAction.FoldersPermissionsWrite, folderDTO); @@ -33,5 +34,6 @@ export function getFolderPermissions(folderDTO?: FolderDTO) { canEditFolders, canSetPermissions, canViewPermissions, + canDeleteDashboards, }; } diff --git a/public/app/features/browse-dashboards/types.ts b/public/app/features/browse-dashboards/types.ts index db797d0b0f6..795d3bb794d 100644 --- a/public/app/features/browse-dashboards/types.ts +++ b/public/app/features/browse-dashboards/types.ts @@ -49,6 +49,7 @@ interface RendererUserProps { onAllSelectionChange?: (newState: boolean) => void; onItemSelectionChange?: (item: DashboardViewItem, newState: boolean) => void; treeID?: string; + permissions?: BrowseDashboardsPermissions; } export type DashboardsTreeColumn = Column; @@ -60,3 +61,10 @@ export enum SelectionState { Selected, Mixed, } + +export interface BrowseDashboardsPermissions { + canEditFolders: boolean; + canEditDashboards: boolean; + canDeleteFolders?: boolean; + canDeleteDashboards?: boolean; +} diff --git a/public/app/features/canvas/runtime/root.tsx b/public/app/features/canvas/runtime/root.tsx index 4a1e8a207c8..512a4d78a90 100644 --- a/public/app/features/canvas/runtime/root.tsx +++ b/public/app/features/canvas/runtime/root.tsx @@ -1,3 +1,5 @@ +import { Fragment } from 'react'; + import { CanvasFrameOptions } from '../frame'; import { FrameState } from './frame'; @@ -49,7 +51,9 @@ export class RootElement extends FrameState { ref={this.setRootRef} style={{ ...this.sizeStyle, ...this.dataStyle }} > - {this.elements.map((v) => v.render())} + {this.elements.map((v) => ( + {v.render()} + ))}
); } diff --git a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.test.tsx b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.test.tsx index 186262741bc..ae5a6d22b10 100644 --- a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.test.tsx +++ b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.test.tsx @@ -3,14 +3,32 @@ import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +import { RepositoryView } from '../../../../api/clients/provisioning/v0alpha1'; import { ProvisionedDashboardFormData } from '../../saving/shared'; import { ResourceEditFormSharedFields } from './ResourceEditFormSharedFields'; +const mockRepo: { github: RepositoryView; local: RepositoryView } = { + github: { + type: 'github', + name: 'test-repo', + title: 'Test Repo', + workflows: ['branch', 'write'], + target: 'folder', + }, + local: { + type: 'local', + name: 'local-repo', + title: 'Local Repo', + workflows: [], + target: 'folder', + }, +}; + // Mock the i18n hook since it's used in the component jest.mock('@grafana/i18n', () => ({ t: (_: string, defaultValue: string) => defaultValue, - Trans: ({ children }: { children: React.ReactNode }) => children, + Trans: ({ children }: { children: ReactNode }) => children, })); interface SetupOptions { @@ -19,7 +37,7 @@ interface SetupOptions { isNew?: boolean; readOnly?: boolean; workflow?: 'write' | 'branch'; - isGitHub?: boolean; + repository?: RepositoryView; } function setup(options: SetupOptions = {}) { @@ -32,7 +50,7 @@ function setup(options: SetupOptions = {}) { isNew, readOnly, workflow, - isGitHub, + repository, } = options; const user = userEvent.setup(); @@ -58,7 +76,7 @@ function setup(options: SetupOptions = {}) { isNew, readOnly, workflow, - isGitHub, + repository, }; return { @@ -80,14 +98,14 @@ describe('ResourceEditFormSharedFields', () => { expect(screen.getByRole('textbox', { name: 'Comment' })).toBeInTheDocument(); }); - it('should not render workflow fields when isGitHub is false', () => { - setup({ isGitHub: false }); + it('should not render workflow fields when repository is false', () => { + setup({ repository: mockRepo.local }); expect(screen.queryByText('Workflow')).not.toBeInTheDocument(); }); - it('should render workflow fields when isGitHub is true', () => { - setup({ isGitHub: true }); + it('should render workflow fields when repository is true', () => { + setup({ repository: mockRepo.github }); expect(screen.getByRole('radiogroup')).toBeInTheDocument(); expect(screen.getByRole('radio', { name: 'Write directly' })).toBeInTheDocument(); @@ -110,8 +128,8 @@ describe('ResourceEditFormSharedFields', () => { expect(commentTextarea).toBeDisabled(); }); - it('should not render workflow fields when readOnly is true and isGitHub is true', () => { - setup({ readOnly: true, isGitHub: true }); + it('should not render workflow fields when readOnly is true and repository is true', () => { + setup({ readOnly: true, repository: mockRepo.github }); expect(screen.queryByText('Workflow')).not.toBeInTheDocument(); }); @@ -119,14 +137,14 @@ describe('ResourceEditFormSharedFields', () => { describe('Workflow Fields', () => { it('should not render branch field when workflow is write', () => { - setup({ formDefaultValues: { workflow: 'write' }, isGitHub: true, workflow: 'write' }); + setup({ formDefaultValues: { workflow: 'write' }, repository: mockRepo.github, workflow: 'write' }); expect(screen.getByText('Workflow')).toBeInTheDocument(); expect(screen.queryByRole('textbox', { name: /branch/i })).not.toBeInTheDocument(); }); it('should render branch field when workflow is branch', () => { - setup({ formDefaultValues: { workflow: 'branch' }, isGitHub: true, workflow: 'branch' }); + setup({ formDefaultValues: { workflow: 'branch' }, repository: mockRepo.github, workflow: 'branch' }); expect(screen.getByText('Workflow')).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: /branch/i })).toBeInTheDocument(); @@ -154,7 +172,7 @@ describe('ResourceEditFormSharedFields', () => { }); it('should allow selecting workflow options', async () => { - const { user } = setup({ isGitHub: true }); + const { user } = setup({ repository: mockRepo.github }); const branchOption = screen.getByRole('radio', { name: 'Create branch' }); await user.click(branchOption); @@ -163,7 +181,11 @@ describe('ResourceEditFormSharedFields', () => { }); it('should allow typing in branch field when workflow is branch', async () => { - const { user } = setup({ formDefaultValues: { workflow: 'branch' }, isGitHub: true, workflow: 'branch' }); + const { user } = setup({ + formDefaultValues: { workflow: 'branch' }, + repository: mockRepo.github, + workflow: 'branch', + }); const branchInput = screen.getByRole('textbox', { name: /branch/i }); await user.type(branchInput, 'feature-branch'); @@ -214,7 +236,11 @@ describe('ResourceEditFormSharedFields', () => { describe('Validation', () => { it('should show validation error for invalid branch name', async () => { - const { user } = setup({ formDefaultValues: { workflow: 'branch' }, isGitHub: true, workflow: 'branch' }); + const { user } = setup({ + formDefaultValues: { workflow: 'branch' }, + repository: mockRepo.github, + workflow: 'branch', + }); const branchInput = screen.getByRole('textbox', { name: /branch/i }); await user.type(branchInput, 'invalid//branch'); // Invalid branch name with consecutive slashes @@ -229,14 +255,14 @@ describe('ResourceEditFormSharedFields', () => { describe('Edge Cases', () => { it('should handle empty workflowOptions', () => { - setup({ workflowOptions: [], isGitHub: true }); + setup({ workflowOptions: [], repository: mockRepo.github }); expect(screen.getByText('Workflow')).toBeInTheDocument(); expect(screen.queryByRole('radio')).not.toBeInTheDocument(); }); it('should handle undefined props', () => { - setup({ readOnly: undefined, isGitHub: undefined }); + setup({ readOnly: undefined, repository: mockRepo.local }); expect(screen.getByRole('textbox', { name: /Path/ })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'Comment' })).toBeInTheDocument(); diff --git a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx index 0582aca19e0..7940da956f0 100644 --- a/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx +++ b/public/app/features/dashboard-scene/components/Provisioned/ResourceEditFormSharedFields.tsx @@ -3,9 +3,11 @@ import { Controller, useFormContext } from 'react-hook-form'; import { t } from '@grafana/i18n'; import { Field, TextArea, Input, RadioButtonGroup } from '@grafana/ui'; +import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; import { BranchValidationError } from 'app/features/provisioning/Shared/BranchValidationError'; import { WorkflowOption } from 'app/features/provisioning/types'; import { validateBranchName } from 'app/features/provisioning/utils/git'; +import { isGitProvider } from 'app/features/provisioning/utils/repositoryTypes'; interface DashboardEditFormSharedFieldsProps { resourceType: 'dashboard' | 'folder'; @@ -13,12 +15,12 @@ interface DashboardEditFormSharedFieldsProps { isNew?: boolean; readOnly?: boolean; workflow?: WorkflowOption; - isGitHub?: boolean; + repository?: RepositoryView; hidePath?: boolean; } export const ResourceEditFormSharedFields = memo( - ({ readOnly = false, workflow, workflowOptions, isGitHub, isNew, resourceType, hidePath = false }) => { + ({ readOnly = false, workflow, workflowOptions, repository, isNew, resourceType, hidePath = false }) => { const { control, register, @@ -64,7 +66,7 @@ export const ResourceEditFormSharedFields = memo {/* Workflow */} - {isGitHub && !readOnly && ( + {repository?.type && isGitProvider(repository.type) && !readOnly && ( <> extends SceneObjectState { value: V; @@ -74,6 +82,22 @@ export abstract class ConditionalRenderingBase< this.notifyChange(); } + public findRule() { + return this.getRenderingGroup().getRule(this.state.key!); + } + + public undoDeleted(index: number, rule: ConditionalRenderingConditions) { + const group = this.getRenderingGroup(); + const restoredState = [...group.state.value]; + restoredState.splice(index, 0, rule); + group.setStateAndNotify({ value: restoredState }); + } + + private getRenderingGroup(): ConditionalRenderingGroup { + // TODO: Adjust once nested rules are introduced to get relevant ConditionalRenderingGroup + return this._getConditionalLogicRoot().state.rootGroup; + } + private _getConditionalLogicRoot(): ConditionalRendering { return sceneGraph.getAncestor(this, ConditionalRendering); } @@ -115,7 +139,20 @@ function ConditionalRenderingBaseRenderer({ model.onDelete()} + onClick={() => { + const { rule, ruleIndex } = model.findRule(); + + dashboardEditActions.edit({ + description: t('dashboard.conditional-rendering.conditions.shared.delete-condition', 'Delete Condition'), + source: model, + perform: () => model.onDelete(), + undo: () => { + if (rule) { + model.undoDeleted(ruleIndex, rule); + } + }, + }); + }} /> diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx index b881486cc01..b39090bb1df 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingData.tsx @@ -6,10 +6,12 @@ import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { ConditionalRenderingDataKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { Combobox, ComboboxOption } from '@grafana/ui'; +import { dashboardEditActions } from '../edit-pane/shared'; import { AutoGridItem } from '../scene/layout-auto-grid/AutoGridItem'; import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; import { ConditionalRenderingSerializerRegistryItem, DataConditionValue, ItemsWithConditionalRendering } from './types'; +import { translatedItemType } from './utils'; type ConditionalRenderingDataState = ConditionalRenderingBaseState; @@ -32,7 +34,7 @@ export class ConditionalRenderingData extends ConditionalRenderingBase model.setStateAndNotify({ value: Boolean(value) })} + onChange={({ value: val }) => { + dashboardEditActions.edit({ + description: t('dashboard.edit-actions.edit-query-result-rule', 'Change query result rule'), + source: model, + perform: () => model.setStateAndNotify({ value: Boolean(val) }), + undo: () => model.setStateAndNotify({ value }), + }); + }} /> ); } diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx index 0e18d5e5e0a..ee949871a8f 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroup.tsx @@ -1,8 +1,13 @@ +import { capitalize, lowerCase } from 'lodash'; +import { useMemo } from 'react'; + import { t } from '@grafana/i18n'; import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { ConditionalRenderingGroupKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { Stack } from '@grafana/ui'; +import { dashboardEditActions } from '../edit-pane/shared'; + import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; import { ConditionalRenderingData } from './ConditionalRenderingData'; import { ConditionalRenderingGroupAdd } from './ConditionalRenderingGroupAdd'; @@ -18,7 +23,9 @@ import { GroupConditionItemType, GroupConditionVisibility, GroupConditionValue, + ConditionalRenderingConditions, } from './types'; +import { translatedItemType } from './utils'; export interface ConditionalRenderingGroupState extends ConditionalRenderingBaseState { visibility: GroupConditionVisibility; @@ -63,7 +70,7 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase condition.state.key !== key) }); } + public removeLastItem() { + const newValues = [...this.state.value]; + newValues.pop(); + this.setStateAndNotify({ value: newValues }); + } + + public getRule(key: string) { + const ruleIndex = this.state.value.findIndex((rule) => rule.state.key === key); + const rule = this.state.value[ruleIndex]; + return { rule, ruleIndex }; + } + public serialize(): ConditionalRenderingGroupKind { if (this.state.value.some((item) => item instanceof ConditionalRenderingGroup)) { throw new Error('ConditionalRenderingGroup cannot contain nested ConditionalRenderingGroups'); @@ -127,22 +150,52 @@ export class ConditionalRenderingGroup extends ConditionalRenderingBase) { const { condition, visibility, value } = model.useState(); const { variables } = sceneGraph.getVariables(model).useState(); + const itemType = useMemo(() => model.getItemType(), [model]); return ( model.changeVisibility(value)} + onChange={(value) => { + dashboardEditActions.edit({ + description: t('dashboard.conditional-rendering.conditions.group.visibility.label', '{{type}} visibility', { + type: capitalize(translatedItemType(itemType)), + }), + source: model, + perform: () => model.changeVisibility(value), + undo: () => model.changeVisibility(visibility), + }); + }} /> {value.length > 1 && ( - model.changeCondition(value)} /> + { + dashboardEditActions.edit({ + description: t('dashboard.conditional-rendering.conditions.group.condition.label', 'Match rules'), + source: model, + perform: () => model.changeCondition(value), + undo: () => model.changeCondition(condition), + }); + }} + /> )} {value.map((entry) => entry.render())} 0} - onAdd={(itemType) => model.addItem(itemType)} + onAdd={({ value, label }) => { + const item = model.createItem(value!); + dashboardEditActions.edit({ + description: t('dashboard.edit-actions.add-conditional-rule', 'Add {{ruleDescription}} rule', { + ruleDescription: lowerCase(label), + }), + source: model, + perform: () => model.addItem(item), + undo: () => model.removeLastItem(), + }); + }} /> ); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx index 110f7a0d14e..e423870f6c8 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupAdd.tsx @@ -9,7 +9,7 @@ import { GroupConditionItemType, ItemsWithConditionalRendering } from './types'; interface Props { itemType: ItemsWithConditionalRendering; hasVariables: boolean; - onAdd: (itemType: GroupConditionItemType) => void; + onAdd: (option: SelectableValue) => void; } export const ConditionalRenderingGroupAdd = ({ itemType, hasVariables, onAdd }: Props) => { @@ -42,7 +42,7 @@ export const ConditionalRenderingGroupAdd = ({ itemType, hasVariables, onAdd }: variant="secondary" label={t('dashboard.conditional-rendering.conditions.group.add.button', 'Add rule')} options={options} - onChange={({ value }) => onAdd(value!)} + onChange={(option) => onAdd(option)} /> ); }; diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx index 3ed91b28c37..0c105d798c5 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx @@ -7,6 +7,7 @@ import { t } from '@grafana/i18n'; import { Field, RadioButtonGroup, useStyles2 } from '@grafana/ui'; import { GroupConditionVisibility, ItemsWithConditionalRendering } from './types'; +import { translatedItemType } from './utils'; interface Props { itemType: ItemsWithConditionalRendering; @@ -28,7 +29,7 @@ export const ConditionalRenderingGroupVisibility = ({ itemType, value, onChange return ( diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx index 4e57cd08347..28033cf18bb 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { rangeUtil, SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -7,8 +7,11 @@ import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { ConditionalRenderingTimeRangeSizeKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { Field, Select, useStyles2 } from '@grafana/ui'; +import { dashboardEditActions } from '../edit-pane/shared'; + import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; import { ConditionalRenderingSerializerRegistryItem, TimeRangeSizeConditionValue } from './types'; +import { translatedItemType } from './utils'; type ConditionalRenderingTimeRangeSizeState = ConditionalRenderingBaseState; @@ -29,7 +32,7 @@ export class ConditionalRenderingTimeRangeSize extends ConditionalRenderingBase< return t( 'dashboard.conditional-rendering.conditions.time-range-size.info', 'Show or hide the {{type}} if the dashboard time range is shorter than the selected time frame.', - { type: this.getItemType() } + { type: translatedItemType(this.getItemType()) } ); } @@ -192,6 +195,18 @@ function ConditionalRenderingTimeRangeSizeRenderer({ model }: SceneComponentProp return [{ label: value, value }, ...staticOptions]; }, [staticOptions, value]); + const handleChange = useCallback( + (newValue: string | undefined) => { + dashboardEditActions.edit({ + description: t('dashboard.edit-actions.edit-time-range-rule', 'Change time range rule'), + source: model, + perform: () => model.setStateAndNotify({ value: newValue }), + undo: () => model.setStateAndNotify({ value }), + }); + }, + [model, value] + ); + return ( model.setStateAndNotify({ value })} + onCreateOption={(value) => handleChange(value)} value={value} options={options} - onChange={({ value }) => model.setStateAndNotify({ value })} + onChange={({ value }) => handleChange(value)} /> ); diff --git a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingVariable.tsx b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingVariable.tsx index c0d1454ba8f..5045f76d885 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingVariable.tsx +++ b/public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingVariable.tsx @@ -5,12 +5,15 @@ import { SceneComponentProps, sceneGraph, VariableDependencyConfig } from '@graf import { ConditionalRenderingVariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import { Box, Combobox, ComboboxOption, Input, Stack } from '@grafana/ui'; +import { dashboardEditActions } from '../edit-pane/shared'; + import { ConditionalRenderingBase, ConditionalRenderingBaseState } from './ConditionalRenderingBase'; import { ConditionalRenderingSerializerRegistryItem, VariableConditionValue, VariableConditionValueOperator, } from './types'; +import { translatedItemType } from './utils'; type ConditionalRenderingVariableState = ConditionalRenderingBaseState; @@ -31,7 +34,7 @@ export class ConditionalRenderingVariable extends ConditionalRenderingBase @@ -118,7 +123,14 @@ function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps model.setStateAndNotify({ value: { ...value, name: option.value } })} + onChange={(option) => { + dashboardEditActions.edit({ + description: undoText, + source: model, + perform: () => model.setStateAndNotify({ value: { ...value, name: option.value } }), + undo: () => model.setStateAndNotify({ value: { ...value, name: value.name } }), + }); + }} /> @@ -127,13 +139,28 @@ function ConditionalRenderingVariableRenderer({ model }: SceneComponentProps model.setStateAndNotify({ value: { ...value, operator: option.value } })} + onChange={(option) => { + dashboardEditActions.edit({ + description: undoText, + source: model, + perform: () => model.setStateAndNotify({ value: { ...value, operator: option.value } }), + undo: () => model.setStateAndNotify({ value: { ...value, operator: value.operator } }), + }); + }} /> model.setStateAndNotify({ value: { ...value, value: e.currentTarget.value } })} + onChange={(e) => { + const eventValue = e.currentTarget.value; + dashboardEditActions.edit({ + description: undoText, + source: model, + perform: () => model.setStateAndNotify({ value: { ...value, value: eventValue } }), + undo: () => model.setStateAndNotify({ value: { ...value, value: value.value } }), + }); + }} /> ); diff --git a/public/app/features/dashboard-scene/conditional-rendering/utils.ts b/public/app/features/dashboard-scene/conditional-rendering/utils.ts index 87c71e723b9..d8e1b841692 100644 --- a/public/app/features/dashboard-scene/conditional-rendering/utils.ts +++ b/public/app/features/dashboard-scene/conditional-rendering/utils.ts @@ -1,3 +1,6 @@ +import { lowerCase } from 'lodash'; + +import { t } from '@grafana/i18n'; import { SceneObject } from '@grafana/scenes'; import { AutoGridItem } from '../scene/layout-auto-grid/AutoGridItem'; @@ -17,3 +20,14 @@ export function getItemType(object: SceneObject): ItemsWithConditionalRendering return 'element'; } + +export const translatedItemType = (item: ItemsWithConditionalRendering) => { + const translations: { [key in ItemsWithConditionalRendering]: string } = { + panel: lowerCase(t('dashboard.edit-pane.elements.panel', 'Panel')), + row: lowerCase(t('dashboard.edit-pane.elements.row', 'Row')), + tab: lowerCase(t('dashboard.edit-pane.elements.tab', 'Tab')), + element: lowerCase(t('dashboard.edit-pane.elements.element', 'Element')), + }; + + return translations[item] || item; +}; diff --git a/public/app/features/dashboard-scene/saving/provisioned/DashboardPreviewBanner.tsx b/public/app/features/dashboard-scene/saving/provisioned/DashboardPreviewBanner.tsx index f44552cbd69..2c23481b5eb 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/DashboardPreviewBanner.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/DashboardPreviewBanner.tsx @@ -48,10 +48,10 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre return ; } - // Check if this is a GitHub link - const githubURL = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL; - if (githubURL) { - return ; + // Check if this is a repo link + const repoUrl = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL; + if (repoUrl) { + return ; } return ( diff --git a/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.test.tsx b/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.test.tsx index 94964f8004a..b742368dbce 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.test.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.test.tsx @@ -2,8 +2,10 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { textUtil } from '@grafana/data'; +import { RepoType } from 'app/features/provisioning/Wizard/types'; +import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam'; -import { PreviewBannerViewPR } from './PreviewBannerViewPR'; +import { isValidRepoType, PreviewBannerViewPR } from './PreviewBannerViewPR'; jest.mock('@grafana/data', () => ({ ...jest.requireActual('@grafana/data'), @@ -12,23 +14,33 @@ jest.mock('@grafana/data', () => ({ }, })); -jest.mock('@grafana/i18n', () => ({ - t: jest.fn((key: string, defaultValue: string) => defaultValue), - Trans: ({ children }: { children: React.ReactNode }) => children, +jest.mock('app/features/provisioning/hooks/usePullRequestParam', () => ({ + usePullRequestParam: jest.fn(), })); const mockTextUtil = jest.mocked(textUtil); -function setup(props: { prParam: string; isFolder?: boolean; isNewPr?: boolean } = { prParam: 'test-url' }) { - const defaultProps = { - isFolder: false, - isNewPr: false, - ...props, +const mockUsePullRequestParam = jest.mocked(usePullRequestParam); + +function setup( + options: { prParam: string; isNewPr?: boolean; repoType?: RepoType } = { prParam: 'test-url', repoType: 'github' } +) { + const componentProps = { + prParam: options.prParam, + isNewPr: options.isNewPr || false, }; - const renderResult = render(); + // Mock the hook BEFORE rendering the component + mockUsePullRequestParam.mockReturnValue({ + prURL: undefined, + newPrURL: undefined, + repoURL: undefined, + repoType: options.repoType || 'github', + }); - return { renderResult, props: defaultProps }; + const renderResult = render(); + + return { renderResult, props: componentProps }; } describe('PreviewBannerViewPR', () => { @@ -57,55 +69,63 @@ describe('PreviewBannerViewPR', () => { describe('Dashboard scenarios', () => { it('should render correct text for new PR dashboard', () => { - setup({ prParam: 'test-url', isFolder: false, isNewPr: true }); + setup({ prParam: 'test-url', isNewPr: true }); expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByText('A new resource has been created in a branch in GitHub.')).toBeInTheDocument(); }); it('should render correct text for existing PR dashboard', () => { - setup({ prParam: 'test-url', isFolder: false, isNewPr: false }); + setup({ prParam: 'test-url', isNewPr: false }); expect(screen.getByRole('status')).toBeInTheDocument(); - expect(screen.getByText('This resource is loaded from a pull request in GitHub.')).toBeInTheDocument(); + expect( + screen.getByText( + 'The rest of Grafana users in your organization will still see the current version saved to configured default branch until this branch is merged' + ) + ).toBeInTheDocument(); }); it('should render correct button text for new PR dashboard', () => { - setup({ prParam: 'test-url', isFolder: false, isNewPr: true }); + setup({ prParam: 'test-url', isNewPr: true }); expect(screen.getByText('Open pull request in GitHub')).toBeInTheDocument(); }); it('should render correct button text for existing PR dashboard', () => { - setup({ prParam: 'test-url', isFolder: false, isNewPr: false }); + setup({ prParam: 'test-url', isNewPr: false }); expect(screen.getByText('View pull request in GitHub')).toBeInTheDocument(); }); }); - describe('Folder scenarios', () => { - it('should render correct text for new PR folder', () => { - setup({ prParam: 'test-url', isFolder: true, isNewPr: true }); + describe('Additional scenarios', () => { + it('should render correct text for new PR resource', () => { + setup({ prParam: 'test-url', isNewPr: true }); expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByText('A new resource has been created in a branch in GitHub.')).toBeInTheDocument(); }); - it('should render correct text for existing PR folder', () => { - setup({ prParam: 'test-url', isFolder: true, isNewPr: false }); + it('should render correct text for existing PR resource', () => { + setup({ prParam: 'test-url', isNewPr: false }); expect(screen.getByRole('status')).toBeInTheDocument(); - expect(screen.getByText('This resource is loaded from a pull request in GitHub.')).toBeInTheDocument(); + expect( + screen.getByText( + 'This resource is loaded from the branch you just created in GitHub and it is only visible to you' + ) + ).toBeInTheDocument(); }); - it('should render correct button text for new PR folder', () => { - setup({ prParam: 'test-url', isFolder: true, isNewPr: true }); + it('should render correct button text for new PR resource', () => { + setup({ prParam: 'test-url', isNewPr: true }); expect(screen.getByText('Open pull request in GitHub')).toBeInTheDocument(); }); - it('should render correct button text for existing PR folder', () => { - setup({ prParam: 'test-url', isFolder: true, isNewPr: false }); + it('should render correct button text for existing PR resource', () => { + setup({ prParam: 'test-url', isNewPr: false }); expect(screen.getByText('View pull request in GitHub')).toBeInTheDocument(); }); @@ -113,7 +133,7 @@ describe('PreviewBannerViewPR', () => { describe('Button functionality', () => { it('should open URL in new tab when button is clicked', async () => { - const testUrl = 'https://github.com/test/repo/pull/123'; + const testUrl = 'https://GitHub.com/test/repo/pull/123'; setup({ prParam: testUrl }); const button = screen.getByRole('button', { name: /close alert/i }); @@ -122,4 +142,46 @@ describe('PreviewBannerViewPR', () => { expect(windowOpenSpy).toHaveBeenCalledWith(testUrl, '_blank'); }); }); + + describe('Different repository types', () => { + it('should handle GitLab repository type', () => { + setup({ prParam: 'test-url', isNewPr: false, repoType: 'gitlab' }); + + expect(screen.getByRole('status')).toBeInTheDocument(); + expect( + screen.getByText( + 'This resource is loaded from the branch you just created in GitLab and it is only visible to you' + ) + ).toBeInTheDocument(); + }); + + it('should handle Bitbucket repository type', () => { + setup({ prParam: 'test-url', isNewPr: false, repoType: 'bitbucket' }); + + expect(screen.getByRole('status')).toBeInTheDocument(); + expect( + screen.getByText( + 'This resource is loaded from the branch you just created in Bitbucket and it is only visible to you' + ) + ).toBeInTheDocument(); + }); + }); +}); + +describe('isValidRepoType', () => { + it('should return true for valid repo types', () => { + expect(isValidRepoType('github')).toBe(true); + expect(isValidRepoType('gitlab')).toBe(true); + expect(isValidRepoType('bitbucket')).toBe(true); + expect(isValidRepoType('git')).toBe(true); + }); + + it('should return false for invalid repo types', () => { + expect(isValidRepoType('unknown')).toBe(false); + expect(isValidRepoType('apple')).toBe(false); + expect(isValidRepoType('')).toBe(false); + expect(isValidRepoType(undefined)).toBe(false); + // @ts-expect-error testing invalid type + expect(isValidRepoType(null)).toBe(false); + }); }); diff --git a/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.tsx b/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.tsx index e1da054147f..da711f084ca 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/PreviewBannerViewPR.tsx @@ -1,30 +1,75 @@ import { textUtil } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { Alert, Icon, Stack } from '@grafana/ui'; +import { RepoTypeDisplay, RepoType } from 'app/features/provisioning/Wizard/types'; +import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam'; import { commonAlertProps } from './DashboardPreviewBanner'; // TODO: We have this https://github.com/grafana/git-ui-sync-project/issues/166 to add more details about the PR. interface Props { - prParam: string; + prParam?: string; isNewPr?: boolean; + behindBranch?: boolean; + repoUrl?: string; } /** - * @description This component is used to display a banner when a provisioned dashboard/folder is created or loaded from a new branch in Github. + * @description This component is used to display a banner when a provisioned dashboard/folder is created or loaded from a new branch in repo. */ -export function PreviewBannerViewPR({ prParam, isNewPr }: Props) { +export function PreviewBannerViewPR({ prParam, isNewPr, behindBranch, repoUrl }: Props) { + const { repoType } = usePullRequestParam(); + + const capitalizedRepoType = isValidRepoType(repoType) ? RepoTypeDisplay[repoType] : 'repository'; + const titleText = isNewPr ? t( - 'provisioned-resource-preview-banner.title-created-branch-git-hub', - 'A new resource has been created in a branch in GitHub.' + 'provisioned-resource-preview-banner.title-created-branch-in-repo', + 'A new resource has been created in a branch in {{repoType}}.', + { + repoType: capitalizedRepoType, + } ) : t( - 'provisioned-resource-preview-banner.title-loaded-pull-request-git-hub', - 'This resource is loaded from a pull request in GitHub.' + 'provisioned-resource-preview-banner.title-loaded-pull-request-in-repo', + 'This resource is loaded from the branch you just created in {{repoType}} and it is only visible to you', + { + repoType: capitalizedRepoType, + } ); + if (behindBranch) { + return ( + + {t('provisioned-resource-preview-banner.preview-banner.open-in-repo-button', 'Open in {{repoType}}', { + repoType: capitalizedRepoType, + })} + + + } + title={t( + 'provisioned-resource-preview-banner.preview-banner.behind-branch-text', + 'This resource is behind the branch in {{repoType}}.', + { + repoType: capitalizedRepoType, + } + )} + onRemove={repoUrl ? () => window.open(textUtil.sanitizeUrl(repoUrl), '_blank') : undefined} + > + + View it in {{ repoType }} to see the latest changes. + + + ); + } + return ( {isNewPr ? t( - 'provisioned-resource-preview-banner.preview-banner.open-pull-request-in-git-hub', - 'Open pull request in GitHub' + 'provisioned-resource-preview-banner.preview-banner.open-pull-request-in-repo', + 'Open pull request in {{repoType}}', + { repoType: capitalizedRepoType } ) : t( - 'provisioned-resource-preview-banner.preview-banner.view-pull-request-in-git-hub', - 'View pull request in GitHub' + 'provisioned-resource-preview-banner.preview-banner.view-pull-request-in-repo', + 'View pull request in {{repoType}}', + { repoType: capitalizedRepoType } )} } - onRemove={() => window.open(textUtil.sanitizeUrl(prParam), '_blank')} + onRemove={prParam ? () => window.open(textUtil.sanitizeUrl(prParam), '_blank') : undefined} > - The value is not yet saved in the Grafana database + The rest of Grafana users in your organization will still see the current version saved to configured default + branch until this branch is merged ); } + +export function isValidRepoType(repoType: string | undefined): repoType is RepoType { + if (typeof repoType !== 'string') { + return false; + } + return repoType in RepoTypeDisplay; +} diff --git a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx index 172cdf914d0..45b1aeb6f49 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx @@ -12,7 +12,7 @@ export interface SaveProvisionedDashboardProps { } export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: SaveProvisionedDashboardProps) { - const { isNew, defaultValues, loadedFromRef, isGitHub, workflowOptions, readOnly } = + const { isNew, defaultValues, loadedFromRef, workflowOptions, readOnly, repository } = useProvisionedDashboardData(dashboard); if (!defaultValues) { @@ -27,7 +27,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Save isNew={isNew} defaultValues={defaultValues} loadedFromRef={loadedFromRef} - isGitHub={isGitHub} + repository={repository} workflowOptions={workflowOptions} readOnly={readOnly} /> diff --git a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.test.tsx b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.test.tsx index a17322b1dfb..18bc3a96cb4 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.test.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.test.tsx @@ -121,7 +121,13 @@ function setup(props: Partial = {}) { diffs: {}, }, isNew: true, - isGitHub: true, + repository: { + type: 'github', + name: 'test-repo', + title: 'Test Repo', + workflows: ['branch', 'write'], + target: 'folder', + }, defaultValues: { ref: 'dashboard/2023-01-01-abcde', path: 'test-dashboard.json', diff --git a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx index 7ffb27f7e9d..4387af82439 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx @@ -7,6 +7,7 @@ import { Trans, t } from '@grafana/i18n'; import { getAppEvents, locationService } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { Alert, Button, Field, Input, Stack, TextArea } from '@grafana/ui'; +import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import kbn from 'app/core/utils/kbn'; import { Resource } from 'app/features/apiserver/types'; @@ -15,6 +16,7 @@ import { PROVISIONING_URL } from 'app/features/provisioning/constants'; import { useCreateOrUpdateRepositoryFile } from 'app/features/provisioning/hooks/useCreateOrUpdateRepositoryFile'; import { ResourceEditFormSharedFields } from '../../components/Provisioned/ResourceEditFormSharedFields'; +import { buildResourceBranchRedirectUrl } from '../../settings/utils'; import { getDashboardUrl } from '../../utils/getDashboardUrl'; import { useProvisionedRequestHandler } from '../../utils/useProvisionedRequestHandler'; import { SaveDashboardFormCommonOptions } from '../SaveDashboardForm'; @@ -26,10 +28,10 @@ import { getProvisionedMeta } from './utils/getProvisionedMeta'; export interface Props extends SaveProvisionedDashboardProps { isNew: boolean; defaultValues: ProvisionedDashboardFormData; - isGitHub: boolean; loadedFromRef?: string; workflowOptions: Array<{ label: string; value: string }>; readOnly: boolean; + repository?: RepositoryView; } export function SaveProvisionedDashboardForm({ @@ -39,9 +41,9 @@ export function SaveProvisionedDashboardForm({ changeInfo, isNew, loadedFromRef, - isGitHub, workflowOptions, readOnly, + repository, }: Props) { const navigate = useNavigate(); const appEvents = getAppEvents(); @@ -91,7 +93,14 @@ export function SaveProvisionedDashboardForm({ const onBranchSuccess = (ref: string, path: string) => { panelEditor?.onDiscard(); drawer.onClose(); - navigate(`${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}?ref=${ref}`); + + const url = buildResourceBranchRedirectUrl({ + baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`, + paramName: 'ref', + paramValue: ref, + repoType: request.data?.repository?.type, + }); + navigate(url); }; useProvisionedRequestHandler({ @@ -221,7 +230,7 @@ export function SaveProvisionedDashboardForm({ readOnly={readOnly} workflow={workflow} workflowOptions={workflowOptions} - isGitHub={isGitHub} + repository={repository} isNew={isNew} /> diff --git a/public/app/features/dashboard-scene/saving/provisioned/hooks.ts b/public/app/features/dashboard-scene/saving/provisioned/hooks.ts index e82ebcee548..8edec0ec7a2 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/hooks.ts +++ b/public/app/features/dashboard-scene/saving/provisioned/hooks.ts @@ -59,7 +59,6 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription, loade workflow: getDefaultWorkflow(repository, loadedFromRef), }, isNew: !meta.k8s?.name, - isGitHub: repository?.type === 'github', repository, }; } @@ -73,7 +72,6 @@ export interface ProvisionedDashboardData { loadedFromRef?: string; workflowOptions: Array<{ label: string; value: string }>; isNew: boolean; - isGitHub: boolean; readOnly: boolean; } @@ -105,12 +103,11 @@ export function useProvisionedDashboardData(dashboard: DashboardScene): Provisio loadedFromRef, workflowOptions: [], isNew: false, - isGitHub: false, readOnly: true, }; } - const { values, isNew, isGitHub, repository } = defaultValuesResult; + const { values, isNew, repository } = defaultValuesResult; const workflowOptions = getWorkflowOptions(repository, loadedFromRef); const readOnly = !repository?.workflows?.length; @@ -122,7 +119,6 @@ export function useProvisionedDashboardData(dashboard: DashboardScene): Provisio loadedFromRef, workflowOptions, isNew, - isGitHub, readOnly, isLoading, setIsLoading, diff --git a/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx b/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx new file mode 100644 index 00000000000..0e0055a2e73 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx @@ -0,0 +1,60 @@ +import { SceneTimeRangeCompare, SceneComponentProps, VizPanel, sceneGraph } from '@grafana/scenes'; +import { TimeCompareOptions } from '@grafana/schema'; + +function hasTimeCompare(options: unknown): options is TimeCompareOptions { + return options != null && typeof options === 'object' && 'timeCompare' in options; +} + +export class CustomTimeRangeCompare extends SceneTimeRangeCompare { + constructor(state: Partial = {}) { + super({ + ...state, + compareWith: undefined, + compareOptions: [], + hideCheckbox: true, + }); + + this.addActivationHandler(() => this._activationHandler()); + } + + private _activationHandler() { + // Subscribe to parent panel's options changes + const vizPanel = sceneGraph.getAncestor(this, VizPanel); + + this._subs.add( + vizPanel.subscribeToState((newState, prevState) => { + const newTimeCompareEnabled = hasTimeCompare(newState.options) && newState.options.timeCompare; + const prevTimeCompareEnabled = hasTimeCompare(prevState.options) && prevState.options.timeCompare; + + // Only act when transitioning from enabled to disabled + if (prevTimeCompareEnabled && !newTimeCompareEnabled) { + this._handleDisable(); + } + }) + ); + } + + private _handleDisable() { + // Only clear state if there's actually a comparison active + if (this.state.compareWith) { + this.setState({ + compareWith: undefined, + }); + } + } + + static Component = function CustomTimeRangeCompareRenderer({ model }: SceneComponentProps) { + // Get the parent VizPanel to check timeCompare option + const vizPanel = sceneGraph.getAncestor(model, VizPanel); + const { options } = vizPanel.useState(); + + // Check if timeCompare is enabled + const isTimeCompareEnabled = hasTimeCompare(options) && options.timeCompare; + + if (!isTimeCompareEnabled) { + return <>; + } + + return ; + }; +} diff --git a/public/app/features/dashboard-scene/scene/DashboardMacro.ts b/public/app/features/dashboard-scene/scene/DashboardMacro.ts index cb74c7f3baf..35705031210 100644 --- a/public/app/features/dashboard-scene/scene/DashboardMacro.ts +++ b/public/app/features/dashboard-scene/scene/DashboardMacro.ts @@ -34,7 +34,12 @@ class DashboardMacro implements FormatVariable { } export function registerDashboardMacro() { - const unregister = sceneUtils.registerVariableMacro('__dashboard', DashboardMacro); + try { + const unregister = sceneUtils.registerVariableMacro('__dashboard', DashboardMacro); - return () => unregister(); + return () => unregister(); + } catch (e) { + console.error('Error registering dashboard macro', e); + return () => {}; + } } diff --git a/public/app/features/dashboard-scene/scene/export/exporters.test.ts b/public/app/features/dashboard-scene/scene/export/exporters.test.ts index 9f9028e2c41..23f8ad327af 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.test.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.test.ts @@ -5,6 +5,8 @@ import { Dashboard, DashboardCursorSync, ThresholdsMode } from '@grafana/schema' import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2_examples'; import { DatasourceVariableKind, + LibraryPanelKind, + PanelKind, QueryVariableKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import config from 'app/core/config'; @@ -50,8 +52,36 @@ jest.mock('@grafana/runtime', () => ({ })); jest.mock('app/features/library-panels/state/api', () => ({ - getLibraryPanel: jest.fn().mockReturnValue( - Promise.resolve({ + getLibraryPanel: jest.fn().mockImplementation((uid: string) => { + if (uid === 'test-library-panel-uid') { + return Promise.resolve({ + name: 'Test Library Panel', + uid: 'test-library-panel-uid', + model: { + type: 'timeseries', + datasource: { + type: 'testdb', + uid: 'gfdb', + }, + targets: [ + { + refId: 'A', + datasource: { + type: 'testdb', + uid: 'gfdb', + }, + }, + ], + id: 123, + title: 'Test Library Panel', + }, + }); + } + if (uid === 'invalid-uid') { + return Promise.reject(new Error('Library panel not found')); + } + // Default behavior for other UIDs + return Promise.resolve({ name: 'Testing lib panel 1', uid: 'abc-123', model: { @@ -60,9 +90,18 @@ jest.mock('app/features/library-panels/state/api', () => ({ type: 'testdb', uid: '${DS_GFDB}', }, + targets: [ + { + refId: 'A', + datasource: { + type: 'testdb', + uid: '${DS_GFDB}', + }, + }, + ], }, - }) - ), + }); + }), })); variableAdapters.register(createQueryVariableAdapter()); @@ -514,6 +553,12 @@ describe('dashboard exporter v1', () => { expect(element.kind).toBe(LibraryElementKind.Panel); expect(element.model).toEqual({ datasource: { type: 'testdb', uid: '${DS_GFDB}' }, + targets: [ + { + datasource: { type: 'testdb', uid: '${DS_GFDB}' }, + refId: 'A', + }, + ], type: 'graph', }); }); @@ -528,6 +573,12 @@ describe('dashboard exporter v1', () => { type: 'testdb', uid: '${DS_GFDB}', }, + targets: [ + { + datasource: { type: 'testdb', uid: '${DS_GFDB}' }, + refId: 'A', + }, + ], }); }); }); @@ -600,15 +651,6 @@ describe('dashboard exporter v2', () => { expect(annotationQuery.spec.datasource?.uid).toBeUndefined(); }); - it('should remove library panels from layout', async () => { - const { dashboard, originalSchema } = await setup(); - const elementRef = 'panel-2'; - const libraryPanel = dashboard.elements[elementRef]; - const origLibraryPanel = originalSchema.elements[elementRef]; - expect(origLibraryPanel.kind).toBe('LibraryPanel'); - expect(libraryPanel).toBeUndefined(); - }); - it('should not remove datasource ref from panel that uses a datasource variable', async () => { const { dashboard } = await setup(); const panel = dashboard.elements['panel-using-datasource-var']; @@ -622,6 +664,121 @@ describe('dashboard exporter v2', () => { uid: '${datasourceVar}', }); }); + + it('should convert library panels to inline panels when sharing externally', async () => { + const setupWithLibraryPanel = async (isSharingExternally: boolean) => { + const schemaCopy = JSON.parse(JSON.stringify(handyTestingSchema)); + + // Add a library panel to test conversion + schemaCopy.elements['test-library-panel'] = { + kind: 'LibraryPanel', + spec: { + id: 123, + title: 'Test Library Panel', + libraryPanel: { + uid: 'test-library-panel-uid', + name: 'Test Library Panel', + }, + }, + }; + + // Handle makeExportableV2 union return type: DashboardV2Spec | { error: unknown } + const dashboard = await makeExportableV2(schemaCopy, isSharingExternally); + if (typeof dashboard === 'object' && 'error' in dashboard) { + throw dashboard.error; + } + return { dashboard, originalSchema: schemaCopy }; + }; + + const { dashboard } = await setupWithLibraryPanel(true); // isSharingExternally = true + + // Library panel should be converted to inline panel + const convertedPanel = dashboard.elements['test-library-panel'] as PanelKind; + expect(convertedPanel.kind).toBe('Panel'); + expect(convertedPanel.spec.id).toBe(123); + + // Check that the panel was properly converted + expect(convertedPanel.spec.data.spec.queries[0].spec.query.kind).toBe('testdb'); + expect(convertedPanel.spec.data.spec.queries[0].spec.refId).toBe('A'); + }); + + it('should keep library panels as-is when not sharing externally', async () => { + const setupWithLibraryPanel = async (isSharingExternally: boolean) => { + const schemaCopy = JSON.parse(JSON.stringify(handyTestingSchema)); + + // Add a library panel + schemaCopy.elements['test-library-panel'] = { + kind: 'LibraryPanel', + spec: { + id: 124, + title: 'Test Library Panel', + libraryPanel: { + uid: 'abc-123', + name: 'Testing lib panel 1', + }, + }, + }; + + // Handle makeExportableV2 union return type: DashboardV2Spec | { error: unknown } + const dashboard = await makeExportableV2(schemaCopy, isSharingExternally); + if (typeof dashboard === 'object' && 'error' in dashboard) { + throw dashboard.error; + } + return { dashboard, originalSchema: schemaCopy }; + }; + + const { dashboard } = await setupWithLibraryPanel(false); // isSharingExternally = false + + // Library panel should remain as library panel + const libraryPanel = dashboard.elements['test-library-panel']; + expect(libraryPanel.kind).toBe('LibraryPanel'); + expect((libraryPanel as LibraryPanelKind).spec.libraryPanel.uid).toBe('abc-123'); + }); + + it('should handle library panel conversion errors gracefully', async () => { + // Mock console.error to avoid Jest warnings + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const setupWithInvalidLibraryPanel = async () => { + const schemaCopy = JSON.parse(JSON.stringify(handyTestingSchema)); + + // Add a library panel with invalid uid that will cause getLibraryPanel to fail + schemaCopy.elements['invalid-library-panel'] = { + kind: 'LibraryPanel', + spec: { + id: 125, + title: 'Invalid Library Panel', + libraryPanel: { + uid: 'invalid-uid', + name: 'Invalid Library Panel', + }, + }, + }; + + // Handle makeExportableV2 union return type: DashboardV2Spec | { error: unknown } + const dashboard = await makeExportableV2(schemaCopy, true); // isSharingExternally = true + if (typeof dashboard === 'object' && 'error' in dashboard) { + throw dashboard.error; + } + + return { dashboard, originalSchema: schemaCopy }; + }; + + const { dashboard } = await setupWithInvalidLibraryPanel(); + + // Should return a placeholder panel + const placeholderPanel = dashboard.elements['invalid-library-panel']; + expect(placeholderPanel.kind).toBe('Panel'); + expect((placeholderPanel as PanelKind).spec.id).toBe(125); + expect((placeholderPanel as PanelKind).spec.title).toBe('Invalid Library Panel'); + expect((placeholderPanel as PanelKind).spec.vizConfig.kind).toBe('text'); + + // Verify console.error was called + expect(consoleSpy).toHaveBeenCalledWith('Failed to load library panel invalid-uid:', expect.any(Error)); + + // Restore console.error + consoleSpy.mockRestore(); + }); }); function getStubInstanceSettings(v: string | DataSourceRef): DataSourceInstanceSettings { diff --git a/public/app/features/dashboard-scene/scene/export/exporters.ts b/public/app/features/dashboard-scene/scene/export/exporters.ts index 4ef9f5a4cbd..5778960dd8b 100644 --- a/public/app/features/dashboard-scene/scene/export/exporters.ts +++ b/public/app/features/dashboard-scene/scene/export/exporters.ts @@ -2,6 +2,7 @@ import { defaults, each, sortBy } from 'lodash'; import { DataSourceRef, PanelPluginMeta, VariableOption, VariableRefresh } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; +import { Panel } from '@grafana/schema'; import { Spec as DashboardV2Spec, PanelKind, @@ -9,20 +10,23 @@ import { AnnotationQueryKind, QueryVariableKind, LibraryPanelRef, + LibraryPanelKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { notifyApp } from 'app/core/actions'; import config from 'app/core/config'; +import { createErrorNotification } from 'app/core/copy/appNotification'; +import { buildPanelKind } from 'app/features/dashboard/api/ResponseTransformers'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel, GridPos } from 'app/features/dashboard/state/PanelModel'; import { getLibraryPanel } from 'app/features/library-panels/state/api'; import { variableRegex } from 'app/features/variables/utils'; +import { dispatch } from 'app/store/store'; import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; import { LibraryElementKind } from '../../../library-panels/types'; import { DashboardJson } from '../../../manage-dashboards/types'; import { isConstant } from '../../../variables/guard'; -import { removePanelRefFromLayout } from './utils'; - export interface InputUsage { libraryPanels?: LibraryPanelRef[]; } @@ -331,7 +335,65 @@ export async function makeExportableV1(dashboard: DashboardModel) { } } -export async function makeExportableV2(dashboard: DashboardV2Spec) { +/** + * Converts a LibraryPanelKind to a PanelKind with embedded panel configuration + */ +async function convertLibraryPanelToInlinePanel(libraryPanelElement: LibraryPanelKind): Promise { + const { libraryPanel, id, title } = libraryPanelElement.spec; + + try { + // Load the full library panel definition + const fullLibraryPanel = await getLibraryPanel(libraryPanel.uid, true); + const panelModel: Panel = fullLibraryPanel.model; + const inlinePanel = buildPanelKind(panelModel); + // keep the original id + inlinePanel.spec.id = id; + return inlinePanel; + } catch (error) { + console.error(`Failed to load library panel ${libraryPanel.uid}:`, error); + + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + dispatch( + notifyApp( + createErrorNotification( + `Unable to load library panel "${libraryPanel.name}": ${errorMessage}. It will appear as a placeholder in the export.` + ) + ) + ); + + // Return a placeholder panel if library panel can't be loaded + return { + kind: 'Panel', + spec: { + id, + title: title || `Library Panel: ${libraryPanel.name}`, + description: '', + links: [], + data: { + kind: 'QueryGroup', + spec: { + queries: [], + transformations: [], + queryOptions: {}, + }, + }, + vizConfig: { + kind: 'text', + spec: { + pluginVersion: '', + options: { + content: `**Library Panel Load Error**\n\nUnable to load library panel: ${libraryPanel.name} (${libraryPanel.uid})\n\nError: ${error instanceof Error ? error.message : 'Unknown error'}`, + mode: 'markdown', + }, + fieldConfig: { defaults: {}, overrides: [] }, + }, + }, + }, + }; + } +} + +export async function makeExportableV2(dashboard: DashboardV2Spec, isSharingExternally = false) { const variableLookup: { [key: string]: any } = {}; // get all datasource variables @@ -367,17 +429,21 @@ export async function makeExportableV2(dashboard: DashboardV2Spec) { try { const elements = dashboard.elements; - const layout = dashboard.layout; // process elements for (const [key, element] of Object.entries(elements)) { if (element.kind === 'Panel') { processPanel(element); } else if (element.kind === 'LibraryPanel') { - // just remove the library panel - delete elements[key]; - // remove reference from layout - removePanelRefFromLayout(layout, key); + if (isSharingExternally) { + // Convert library panel to inline panel for external sharing + const inlinePanel = await convertLibraryPanelToInlinePanel(element); + // Apply datasource templating to the converted panel + processPanel(inlinePanel); + // Replace the library panel with the inline panel + elements[key] = inlinePanel; + } + // For internal exports, keep library panels as-is } } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeater.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeater.tsx index a8e473e7a6b..9d1a602fce4 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeater.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeater.tsx @@ -61,7 +61,9 @@ export function RowItemRepeater({ return ( <> - {repeatedRows?.map((rowClone) => )} + {repeatedRows?.map((rowClone) => ( + + ))} ); } diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index 0e39748c270..86dfce4fc4e 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -40,6 +40,9 @@ jest.mock('@grafana/runtime', () => ({ config: { ...jest.requireActual('@grafana/runtime').config, bootData: { + user: { + timezone: 'UTC', + }, settings: { defaultDatasource: '-- Grafana --', datasources: { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts index b5f9950b5f6..fce584cf4f7 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts @@ -1,6 +1,8 @@ -import { PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { PanelQueryKind, PanelKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { getRuntimePanelDataSource } from './utils'; +import { CustomTimeRangeCompare } from '../../scene/CustomTimeRangeCompare'; + +import { buildVizPanel, getRuntimePanelDataSource } from './utils'; // Mock the config needed for the function jest.mock('@grafana/runtime', () => ({ @@ -32,9 +34,81 @@ jest.mock('@grafana/runtime', () => ({ }, }, }, + featureToggles: { + timeComparison: false, + }, }, })); +// Mock only what's essential for header actions tests +jest.mock('../../scene/CustomTimeRangeCompare', () => ({ + CustomTimeRangeCompare: jest.fn(), +})); + +// Helper function to create a minimal panel for testing +const createTestPanel = (): PanelKind => ({ + kind: 'Panel', + spec: { + id: 1, + title: 'Test Panel', + description: '', + vizConfig: { + kind: 'timeseries', + spec: { + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + pluginVersion: '1.0.0', + }, + }, + data: { + kind: 'QueryGroup', + spec: { + queries: [], + queryOptions: {}, + transformations: [], + }, + }, + links: [], + }, +}); + +describe('buildVizPanel', () => { + describe('header actions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should include CustomTimeRangeCompare in headerActions when timeComparison feature toggle is enabled', () => { + // Mock config with timeComparison enabled + const mockConfig = require('@grafana/runtime').config; + mockConfig.featureToggles.timeComparison = true; + + const panel = createTestPanel(); + const vizPanel = buildVizPanel(panel); + + expect(vizPanel.state.headerActions).toBeDefined(); + expect(vizPanel.state.headerActions).toHaveLength(1); + expect(CustomTimeRangeCompare).toHaveBeenCalledWith({ + key: 'time-compare', + compareWith: undefined, + compareOptions: [], + }); + }); + + it('should not include headerActions when timeComparison feature toggle is disabled', () => { + // Mock config with timeComparison disabled + const mockConfig = require('@grafana/runtime').config; + mockConfig.featureToggles.timeComparison = false; + + const panel = createTestPanel(); + const vizPanel = buildVizPanel(panel); + + expect(vizPanel.state.headerActions).toBeUndefined(); + expect(CustomTimeRangeCompare).not.toHaveBeenCalled(); + }); + }); +}); + describe('getRuntimePanelDataSource', () => { it('should return the datasource when it is specified in the query', () => { const query: PanelQueryKind = { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index c8018a98be0..b3b3325ab21 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -25,6 +25,7 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering'; import { ConditionalRenderingGroup } from '../../conditional-rendering/ConditionalRenderingGroup'; import { conditionalRenderingSerializerRegistry } from '../../conditional-rendering/serializers'; +import { CustomTimeRangeCompare } from '../../scene/CustomTimeRangeCompare'; import { DashboardDatasourceBehaviour } from '../../scene/DashboardDatasourceBehaviour'; import { DashboardScene } from '../../scene/DashboardScene'; import { LibraryPanelBehavior } from '../../scene/LibraryPanelBehavior'; @@ -73,6 +74,9 @@ export function buildVizPanel(panel: PanelKind, id?: number): VizPanel { $behaviors: [], extendPanelContext: setDashboardPanelContext, // _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), //FIXME: Angular Migration + headerActions: config.featureToggles.timeComparison + ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] + : undefined, }; if (!config.publicDashboardAccessToken) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index ef03bdd17ee..7e9302634cd 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -810,6 +810,41 @@ describe('transformSaveModelToScene', () => { expect((libPanelBehavior as LibraryPanelBehavior).state.name).toEqual(panel.libraryPanel.name); expect(gridItem.state.body.state.title).toEqual(panel.title); }); + + describe('header actions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should include headerActions when timeComparison feature toggle is enabled', () => { + config.featureToggles.timeComparison = true; + + const panel = { + title: 'Test Panel', + type: 'timeseries', + gridPos: { x: 0, y: 0, w: 12, h: 8 }, + }; + + const { vizPanel } = buildGridItemForTest(panel); + + expect(vizPanel.state.headerActions).toBeDefined(); + expect(vizPanel.state.headerActions).toHaveLength(1); + }); + + it('should not include headerActions when timeComparison feature toggle is disabled', () => { + config.featureToggles.timeComparison = false; + + const panel = { + title: 'Test Panel', + type: 'timeseries', + gridPos: { x: 0, y: 0, w: 12, h: 8 }, + }; + + const { vizPanel } = buildGridItemForTest(panel); + + expect(vizPanel.state.headerActions).toBeUndefined(); + }); + }); }); describe('Convert to new rows', () => { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 2936ccfcf49..37030a95b89 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -29,6 +29,7 @@ import { DashboardDTO, DashboardDataDTO } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; +import { CustomTimeRangeCompare } from '../scene/CustomTimeRangeCompare'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -407,6 +408,9 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { $behaviors: [], extendPanelContext: setDashboardPanelContext, _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), + headerActions: config.featureToggles.timeComparison + ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] + : undefined, }; if (panel.libraryPanel) { diff --git a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardDrawer.tsx b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardDrawer.tsx index cc4d267e8d0..43f02e399e4 100644 --- a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardDrawer.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardDrawer.tsx @@ -13,7 +13,7 @@ export interface Props { * Drawer component for deleting a git provisioned dashboard. */ export function DeleteProvisionedDashboardDrawer({ dashboard, onDismiss }: Props) { - const { defaultValues, loadedFromRef, readOnly, isGitHub, workflowOptions, isNew } = + const { defaultValues, loadedFromRef, readOnly, workflowOptions, isNew, repository } = useProvisionedDashboardData(dashboard); if (!defaultValues) { @@ -26,7 +26,7 @@ export function DeleteProvisionedDashboardDrawer({ dashboard, onDismiss }: Props defaultValues={defaultValues} loadedFromRef={loadedFromRef} readOnly={readOnly} - isGitHub={isGitHub} + repository={repository} isNew={isNew} workflowOptions={workflowOptions} onDismiss={onDismiss} diff --git a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.test.tsx b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.test.tsx index 128213ff1c7..9b05a88bf46 100644 --- a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.test.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.test.tsx @@ -107,7 +107,6 @@ function setup(options: SetupOptions = {}) { }, loadedFromRef: 'main', readOnly: false, - isGitHub: true, workflowOptions: [ { label: 'Branch', value: 'branch' }, { label: 'Write', value: 'write' }, diff --git a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.tsx b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.tsx index e589205ff1c..53bb342cd21 100644 --- a/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardForm.tsx @@ -5,7 +5,7 @@ import { AppEvents } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { getAppEvents } from '@grafana/runtime'; import { Alert, Button, Drawer, Stack } from '@grafana/ui'; -import { useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1'; +import { RepositoryView, useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1'; import { PROVISIONING_URL } from 'app/features/provisioning/constants'; import { ResourceEditFormSharedFields } from '../components/Provisioned/ResourceEditFormSharedFields'; @@ -13,14 +13,16 @@ import { ProvisionedDashboardFormData } from '../saving/shared'; import { DashboardScene } from '../scene/DashboardScene'; import { useProvisionedRequestHandler } from '../utils/useProvisionedRequestHandler'; +import { buildResourceBranchRedirectUrl } from './utils'; + export interface Props { dashboard: DashboardScene; defaultValues: ProvisionedDashboardFormData; readOnly: boolean; - isGitHub: boolean; isNew?: boolean; workflowOptions: Array<{ label: string; value: string }>; loadedFromRef?: string; + repository?: RepositoryView; onDismiss: () => void; } @@ -33,9 +35,9 @@ export function DeleteProvisionedDashboardForm({ defaultValues, loadedFromRef, readOnly, - isGitHub, isNew, workflowOptions, + repository, onDismiss, }: Props) { const methods = useForm({ defaultValues }); @@ -82,9 +84,13 @@ export function DeleteProvisionedDashboardForm({ const onBranchSuccess = (path: string, urls?: Record) => { panelEditor?.onDiscard(); onDismiss(); - navigate( - `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}?pull_request_url=${urls?.newPullRequestURL}` - ); + const url = buildResourceBranchRedirectUrl({ + baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`, + paramName: 'pull_request_url', + paramValue: urls?.newPullRequestURL, + repoType: request.data?.repository?.type, + }); + navigate(url); }; useProvisionedRequestHandler({ @@ -127,7 +133,7 @@ export function DeleteProvisionedDashboardForm({ readOnly={readOnly} workflow={workflow} workflowOptions={workflowOptions} - isGitHub={isGitHub} + repository={repository} /> {/* Save / Cancel button */} diff --git a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardDrawer.tsx b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardDrawer.tsx index b1caef27bf5..63bc6566680 100644 --- a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardDrawer.tsx +++ b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardDrawer.tsx @@ -18,7 +18,7 @@ export function MoveProvisionedDashboardDrawer({ onDismiss, onSuccess, }: Props) { - const { defaultValues, loadedFromRef, readOnly, isGitHub, workflowOptions, isNew } = + const { defaultValues, loadedFromRef, readOnly, workflowOptions, isNew, repository } = useProvisionedDashboardData(dashboard); if (!defaultValues) { @@ -31,7 +31,7 @@ export function MoveProvisionedDashboardDrawer({ defaultValues={defaultValues} loadedFromRef={loadedFromRef} readOnly={readOnly} - isGitHub={isGitHub} + repository={repository} isNew={isNew} workflowOptions={workflowOptions} targetFolderUID={targetFolderUID} diff --git a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.test.tsx b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.test.tsx index 1e6496d4229..cbcefb075a4 100644 --- a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.test.tsx +++ b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.test.tsx @@ -5,7 +5,6 @@ import { getAppEvents } from '@grafana/runtime'; import { useGetFolderQuery } from 'app/api/clients/folder/v1beta1'; import { useCreateRepositoryFilesWithPathMutation, - useDeleteRepositoryFilesWithPathMutation, useGetRepositoryFilesWithPathQuery, } from 'app/api/clients/provisioning/v0alpha1'; import { AnnoKeySourcePath } from 'app/features/apiserver/types'; @@ -26,7 +25,13 @@ jest.mock('@grafana/runtime', () => { jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ useGetRepositoryFilesWithPathQuery: jest.fn(), useCreateRepositoryFilesWithPathMutation: jest.fn(), - useDeleteRepositoryFilesWithPathMutation: jest.fn(), + provisioningAPIv0alpha1: { + endpoints: { + listRepository: { + select: jest.fn(() => () => ({ data: { items: [] } })), + }, + }, + }, })); jest.mock('app/api/clients/folder/v1beta1', () => ({ @@ -70,7 +75,13 @@ function setup(props: Partial = {}) { folder: { uid: '', title: '' }, }, readOnly: false, - isGitHub: true, + repository: { + type: 'github', + name: 'test-repo', + title: 'Test Repo', + workflows: ['branch', 'write'], + target: 'folder', + }, workflowOptions: [ { label: 'Write', value: 'write' }, { label: 'Branch', value: 'branch' }, @@ -96,13 +107,6 @@ const mockCreateRequest = { error: null, }; -const mockDeleteRequest = { - isSuccess: false, - isError: false, - isLoading: false, - error: null, -}; - describe('MoveProvisionedDashboardForm', () => { beforeEach(() => { jest.clearAllMocks(); @@ -143,8 +147,6 @@ describe('MoveProvisionedDashboardForm', () => { (useCreateRepositoryFilesWithPathMutation as jest.Mock).mockReturnValue([jest.fn(), mockCreateRequest]); - (useDeleteRepositoryFilesWithPathMutation as jest.Mock).mockReturnValue([jest.fn(), mockDeleteRequest]); - (useProvisionedRequestHandler as jest.Mock).mockReturnValue(undefined); }); diff --git a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.tsx b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.tsx index 6a17c8df03b..5a2a07cc478 100644 --- a/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard-scene/settings/MoveProvisionedDashboardForm.tsx @@ -8,8 +8,8 @@ import { getAppEvents } from '@grafana/runtime'; import { Alert, Button, Drawer, Field, Input, Spinner, Stack } from '@grafana/ui'; import { useGetFolderQuery } from 'app/api/clients/folder/v1beta1'; import { + RepositoryView, useCreateRepositoryFilesWithPathMutation, - useDeleteRepositoryFilesWithPathMutation, useGetRepositoryFilesWithPathQuery, } from 'app/api/clients/provisioning/v0alpha1'; import { AnnoKeySourcePath } from 'app/features/apiserver/types'; @@ -19,16 +19,18 @@ import { ProvisionedDashboardFormData } from '../saving/shared'; import { DashboardScene } from '../scene/DashboardScene'; import { useProvisionedRequestHandler } from '../utils/useProvisionedRequestHandler'; +import { buildResourceBranchRedirectUrl } from './utils'; + export interface Props { dashboard: DashboardScene; defaultValues: ProvisionedDashboardFormData; readOnly: boolean; - isGitHub: boolean; isNew?: boolean; workflowOptions: Array<{ label: string; value: string }>; loadedFromRef?: string; targetFolderUID?: string; targetFolderTitle?: string; + repository?: RepositoryView; onDismiss: () => void; onSuccess: (folderUID: string, folderTitle: string) => void; } @@ -38,16 +40,17 @@ export function MoveProvisionedDashboardForm({ defaultValues, loadedFromRef, readOnly, - isGitHub, isNew, workflowOptions, targetFolderUID, targetFolderTitle, + repository, onDismiss, onSuccess, }: Props) { const methods = useForm({ defaultValues }); const { editPanel: panelEditor } = dashboard.useState(); + const { handleSubmit, watch } = methods; const appEvents = getAppEvents(); @@ -60,8 +63,7 @@ export function MoveProvisionedDashboardForm({ const { data: targetFolder } = useGetFolderQuery({ name: targetFolderUID! }, { skip: !targetFolderUID }); - const [createFile, createRequest] = useCreateRepositoryFilesWithPathMutation(); - const [deleteFile, deleteRequest] = useDeleteRepositoryFilesWithPathMutation(); + const [moveFile, moveRequest] = useCreateRepositoryFilesWithPathMutation(); const [targetPath, setTargetPath] = useState(''); const navigate = useNavigate(); @@ -99,32 +101,15 @@ export function MoveProvisionedDashboardForm({ const commitMessage = comment || `Move dashboard: ${dashboard.state.title}`; try { - await createFile({ + await moveFile({ name: repo, path: targetPath, ref: branchRef, message: commitMessage, body: currentFileData.resource.file, - }).unwrap(); - - await deleteFile({ - name: repo, - path: path, - ref: branchRef, - message: commitMessage, + originalPath: path, }).unwrap(); } catch (error) { - if (createRequest.isSuccess && !deleteRequest.isSuccess) { - appEvents.publish({ - type: AppEvents.alertWarning.name, - payload: [ - t( - 'dashboard-scene.move-provisioned-dashboard-form.partial-failure-warning', - 'Dashboard was created at new location but could not be deleted from original location. Please manually remove the old file.' - ), - ], - }); - } appEvents.publish({ type: AppEvents.alertError.name, payload: [t('dashboard-scene.move-provisioned-dashboard-form.api-error', 'Failed to move dashboard'), error], @@ -142,12 +127,17 @@ export function MoveProvisionedDashboardForm({ const onBranchSuccess = () => { panelEditor?.onDiscard(); - navigate(`/dashboards?new_pull_request_url=${createRequest.data?.urls?.newPullRequestURL}`); + const url = buildResourceBranchRedirectUrl({ + paramName: 'new_pull_request_url', + paramValue: moveRequest?.data?.urls?.newPullRequestURL, + repoType: moveRequest?.data?.repository?.type, + }); + navigate(url); }; useProvisionedRequestHandler({ dashboard, - request: createRequest, + request: moveRequest, workflow, handlers: { onBranchSuccess, @@ -155,7 +145,7 @@ export function MoveProvisionedDashboardForm({ }, }); - const isLoading = createRequest.isLoading || deleteRequest.isLoading; + const isLoading = moveRequest.isLoading; return ( diff --git a/public/app/features/dashboard-scene/settings/utils.ts b/public/app/features/dashboard-scene/settings/utils.ts index 88011f08b00..741830b8ec5 100644 --- a/public/app/features/dashboard-scene/settings/utils.ts +++ b/public/app/features/dashboard-scene/settings/utils.ts @@ -111,3 +111,30 @@ export function createDashboardEditViewFor(editview: string): DashboardEditView return new GeneralSettingsEditView({}); } } + +export type ResourceBranchUrlOptions = { + baseUrl?: string; + paramName?: string; + paramValue?: string; + repoType?: string; +}; + +export function buildResourceBranchRedirectUrl({ + baseUrl = '/dashboards', + paramName, + paramValue, + repoType, +}: ResourceBranchUrlOptions): string { + const params = new URLSearchParams(); + + if (paramName && paramValue) { + params.set(paramName, paramValue); + } + + if (repoType) { + params.set('repo_type', repoType); + } + + const queryString = params.toString(); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; +} diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx index fcdf7881fce..29183c79531 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx @@ -4,6 +4,7 @@ import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/d import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { EditorField } from '@grafana/plugin-ui'; +import { config } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; import { Alert, CodeEditor, Field, Switch, Box } from '@grafana/ui'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; @@ -61,7 +62,14 @@ export function AdHocVariableForm({ htmlFor="data-source-picker" tooltip={infoText} > - + diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx index edc4aaf3384..3ec58d8aecb 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx +++ b/public/app/features/dashboard-scene/sharing/ExportButton/ResourceExport.tsx @@ -4,7 +4,7 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Dashboard } from '@grafana/schema/dist/esm/index.gen'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; -import { Alert, Label, RadioButtonGroup, Stack, Switch, TextLink } from '@grafana/ui'; +import { Alert, Label, RadioButtonGroup, Stack, Switch } from '@grafana/ui'; import { DashboardJson } from 'app/features/manage-dashboards/types'; import { ExportableResource } from '../ShareExportTab'; @@ -127,19 +127,13 @@ export function ResourceExport({ - The dynamic dashboard functionality is experimental, and has not full feature parity with current dashboards - behaviour. It is based on a new schema format, that does not support library panels. This means that when - exporting the dashboard to use it in another instance, we will not include library panels. We intend to - support them as we progress in the feature{' '} - - life cycle - - . + Due to limitations in the new dashboard schema (V2), library panels will be converted to regular panels with + embedded content during external export. )} diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.test.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.test.tsx index fb416a280fb..19791d70670 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.test.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.test.tsx @@ -1,7 +1,11 @@ import { config } from '@grafana/runtime'; import { SceneTimeRange } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema/dist/esm/index.gen'; -import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { + Spec as DashboardV2Spec, + defaultQueryGroupKind, + defaultVizConfigSpec, +} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; import * as ResponseTransformers from 'app/features/dashboard/api/ResponseTransformers'; import { DashboardJson } from 'app/features/manage-dashboards/types'; import { DashboardDataDTO } from 'app/types/dashboard'; @@ -24,6 +28,7 @@ describe('ShareExportTab', () => { beforeEach(() => { config.featureToggles.kubernetesDashboards = true; + config.featureToggles.dashboardNewLayouts = false; // Set up spies on the functions we want to track transformV2ToV1Spy = jest.spyOn(ResponseTransformers, 'transformDashboardV2SpecToV1').mockReturnValue({ @@ -186,6 +191,88 @@ describe('ShareExportTab', () => { // Should report correct initial version expect(result.initialSaveModelVersion).toBe('v2'); }); + + // If V1 dashboard → V2 Resource should detect library panels correctly + it('should detect library panels in V1 dashboard when exporting as V2 resource', async () => { + const tab = buildV1DashboardWithLibraryPanels(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should detect library panels from V1 dashboard + expect(result.hasLibraryPanels).toBe(true); + expect(result.initialSaveModelVersion).toBe('v1'); + }); + + // If V1 dashboard with dashboardNewLayouts disabled → V2 Resource should detect library panels correctly + it('should detect library panels in V1 dashboard when user selects V2Resource export mode', async () => { + const tab = buildV1DashboardWithLibraryPanels(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should detect library panels from V1 dashboard (first branch of the logic) + expect(result.hasLibraryPanels).toBe(true); + expect(result.initialSaveModelVersion).toBe('v1'); + }); + + // If V1 dashboard without library panels → V2 Resource should return false + it('should return false for hasLibraryPanels when V1 dashboard has no library panels', async () => { + const tab = buildV1DashboardScenario(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should not detect library panels + expect(result.hasLibraryPanels).toBe(false); + expect(result.initialSaveModelVersion).toBe('v1'); + }); + }); + + describe('V2Resource export mode with dashboardNewLayouts disabled', () => { + beforeEach(() => { + config.featureToggles.dashboardNewLayouts = false; + }); + + afterEach(() => { + config.featureToggles.dashboardNewLayouts = true; + }); + + // If V2 dashboard → V2 Resource should detect library panels correctly + it('should detect library panels in V2 dashboard when exporting as V2 resource', async () => { + const tab = buildV2DashboardWithLibraryPanels(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should detect library panels from V2 dashboard elements (second branch of the logic) + expect(result.hasLibraryPanels).toBe(true); + expect(result.initialSaveModelVersion).toBe('v2'); + }); + + // Test the second branch: V2 dashboard with V1 initial save model + it('should detect library panels in V2 dashboard with V1 initial save model', async () => { + const tab = buildV2DashboardWithV1InitialSaveModel(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should detect library panels from V2 dashboard elements (second branch of the logic) + expect(result.hasLibraryPanels).toBe(true); + expect(result.initialSaveModelVersion).toBe('v1'); + }); + + // If V2 dashboard without library panels → V2 Resource should return false + it('should return false for hasLibraryPanels when V2 dashboard has no library panels', async () => { + const tab = buildV2DashboardScenario(); + tab.setState({ exportMode: ExportMode.V2Resource }); + + const result = await tab.getExportableDashboardJson(); + + // Should not detect library panels + expect(result.hasLibraryPanels).toBe(false); + expect(result.initialSaveModelVersion).toBe('v2'); + }); }); describe('Classic export mode', () => { @@ -247,13 +334,37 @@ describe('ShareExportTab', () => { }); }); - // Helper functions to create test scenarios - function buildV1DashboardScenario(): ShareExportTab { + // Helper factory to create test scenarios + function createDashboardScenario(options: { + version: 'v1' | 'v2'; + hasLibraryPanels?: boolean; + initialSaveModelVersion?: 'v1' | 'v2'; + }): ShareExportTab { + const { version, hasLibraryPanels = false, initialSaveModelVersion = version } = options; + + // Create V1 dashboard const mockV1Dashboard: DashboardDataDTO = { - title: 'Test Dashboard V1', + title: `Test Dashboard V1`, uid: 'test-uid-v1', version: 1, - panels: [], + panels: hasLibraryPanels + ? [ + { + id: 1, + type: 'stat', + title: 'Regular Panel', + gridPos: { x: 0, y: 0, w: 12, h: 8 }, + targets: [], + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }, + { + id: 2, + type: 'library-panel-ref', + libraryPanel: { uid: 'lib-panel-uid', name: 'My Library Panel' }, + }, + ] + : [], time: { from: 'now-6h', to: 'now' }, timepicker: {}, timezone: '', @@ -265,36 +376,42 @@ describe('ShareExportTab', () => { templating: { list: [] }, }; - const tab = new ShareExportTab({}); - const scene = new DashboardScene({ - title: 'Test Dashboard V1', - uid: 'test-uid-v1', - meta: { canEdit: true }, - $timeRange: new SceneTimeRange({}), - body: DefaultGridLayoutManager.fromVizPanels([]), - overlay: tab, - }); - - const mockExportableDashboard: DashboardJson = { - ...mockV1Dashboard, - panels: [], - } as DashboardJson; - scene.serializer.getSaveModel = jest.fn(() => mockV1Dashboard); - scene.serializer.makeExportableExternally = jest.fn(() => Promise.resolve(mockExportableDashboard)); - scene.serializer.apiVersion = 'dashboard.grafana.app/v1beta1'; - scene.getInitialSaveModel = jest.fn(() => mockV1Dashboard); - - return tab; - } - - function buildV2DashboardScenario(): ShareExportTab { + // Create V2 dashboard const mockV2Dashboard: DashboardV2Spec = { - title: 'Test Dashboard V2', + title: `Test Dashboard V2`, annotations: [], cursorSync: 'Off', description: 'Test V2 dashboard', editable: true, - elements: {}, + elements: hasLibraryPanels + ? { + 'element-1': { + kind: 'Panel', + spec: { + id: 1, + title: 'Regular Panel', + description: '', + links: [], + data: defaultQueryGroupKind(), + vizConfig: { + kind: 'stat', + spec: defaultVizConfigSpec(), + }, + }, + }, + 'element-2': { + kind: 'LibraryPanel', + spec: { + id: 2, + title: 'My Library Panel', + libraryPanel: { + uid: 'lib-panel-uid', + name: 'My Library Panel', + }, + }, + }, + } + : {}, layout: { kind: 'GridLayout', spec: { items: [] } }, links: [], liveNow: false, @@ -315,19 +432,40 @@ describe('ShareExportTab', () => { const tab = new ShareExportTab({}); const scene = new DashboardScene({ - title: 'Test Dashboard V2', - uid: 'test-uid-v2', + title: `Test Dashboard ${version.toUpperCase()}`, + uid: `test-uid-${version}`, meta: { canEdit: true }, $timeRange: new SceneTimeRange({}), body: DefaultGridLayoutManager.fromVizPanels([]), overlay: tab, }); - scene.serializer.getSaveModel = jest.fn(() => mockV2Dashboard); - scene.serializer.makeExportableExternally = jest.fn(() => Promise.resolve(mockV2Dashboard)); - scene.serializer.apiVersion = 'dashboard.grafana.app/v2alpha1'; - scene.getInitialSaveModel = jest.fn(() => mockV2Dashboard); + // Set up the scene based on current version + const currentDashboard = version === 'v1' ? mockV1Dashboard : mockV2Dashboard; + const initialSaveModel = initialSaveModelVersion === 'v1' ? mockV1Dashboard : mockV2Dashboard; + const apiVersion = version === 'v1' ? 'dashboard.grafana.app/v1beta1' : 'dashboard.grafana.app/v2alpha1'; + + scene.serializer.getSaveModel = jest.fn(() => currentDashboard); + scene.serializer.makeExportableExternally = jest.fn(() => + Promise.resolve( + version === 'v1' ? ({ ...mockV1Dashboard, panels: mockV1Dashboard.panels } as DashboardJson) : mockV2Dashboard + ) + ); + scene.serializer.apiVersion = apiVersion; + scene.getInitialSaveModel = jest.fn(() => initialSaveModel); return tab; } + + // util functions for common scenarios + const buildV1DashboardScenario = () => createDashboardScenario({ version: 'v1' }); + const buildV2DashboardScenario = () => createDashboardScenario({ version: 'v2' }); + const buildV1DashboardWithLibraryPanels = () => createDashboardScenario({ version: 'v1', hasLibraryPanels: true }); + const buildV2DashboardWithLibraryPanels = () => createDashboardScenario({ version: 'v2', hasLibraryPanels: true }); + const buildV2DashboardWithV1InitialSaveModel = () => + createDashboardScenario({ + version: 'v2', + hasLibraryPanels: true, + initialSaveModelVersion: 'v1', + }); }); diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx index 1c19882b877..8a9087f118b 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx @@ -124,12 +124,27 @@ export class ShareExportTab extends SceneObjectBase impleme exportMode: ExportMode.V2Resource, }); + // For automatic V2 path, also process library panels when sharing externally + let finalSpec = exportable; + if (isSharingExternally && isDashboardV2Spec(exportable)) { + const specCopy = JSON.parse(JSON.stringify(exportable)); + const result = await makeExportableV2(specCopy, isSharingExternally); + if ('error' in result) { + return { + json: { error: result.error }, + initialSaveModelVersion, + hasLibraryPanels: Object.values(origDashboard.elements).some((element) => element.kind === 'LibraryPanel'), + }; + } + finalSpec = result; + } + return { json: { apiVersion: scene.serializer.apiVersion ?? '', kind: 'Dashboard', metadata, - spec: exportable, + spec: finalSpec, status: {}, }, initialSaveModelVersion, @@ -203,8 +218,17 @@ export class ShareExportTab extends SceneObjectBase impleme if (exportMode === ExportMode.V2Resource) { const spec = transformSceneToSaveModelSchemaV2(scene); const specCopy = JSON.parse(JSON.stringify(spec)); - const statelessSpec = await makeExportableV2(specCopy); + const statelessSpec = await makeExportableV2(specCopy, isSharingExternally); const exportableV2 = isSharingExternally ? statelessSpec : spec; + // Check if dashboard contains library panels based on dashboard version + let hasLibraryPanels = false; + // Case: V1 dashboard loaded (with kubernetesDashboards enabled and dashboardNewLayouts disabled), and user explicitly selected V2Resource export mode + if (initialSaveModelVersion === 'v1' && !isDashboardV2Spec(origDashboard)) { + hasLibraryPanels = hasLibraryPanelsInV1Dashboard(origDashboard); + } else if (isDashboardV2Spec(origDashboard)) { + // Case: V2 dashboard (either originally V2 or transformed from V1) being exported as V2Resource + hasLibraryPanels = Object.values(origDashboard.elements).some((element) => element.kind === 'LibraryPanel'); + } return { json: { @@ -216,10 +240,7 @@ export class ShareExportTab extends SceneObjectBase impleme status: {}, }, initialSaveModelVersion, - hasLibraryPanels: - initialSaveModelVersion === 'v1' && !isDashboardV2Spec(origDashboard) - ? hasLibraryPanelsInV1Dashboard(origDashboard) - : false, + hasLibraryPanels, }; } diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 7a5c41a08cd..1ab2c72969a 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -17,6 +17,7 @@ import { Dashboard, Panel, RowPanel } from '@grafana/schema'; import { createLogger } from '@grafana/ui'; import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer'; +import { CustomTimeRangeCompare } from '../scene/CustomTimeRangeCompare'; import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; import { DashboardLayoutOrchestrator } from '../scene/DashboardLayoutOrchestrator'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; @@ -343,6 +344,9 @@ export function getDefaultVizPanel(): VizPanel { menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior], }), + headerActions: config.featureToggles.timeComparison + ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] + : undefined, $data: new SceneDataTransformer({ $data: new SceneQueryRunner({ queries: [{ refId: 'A' }], diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 509271f7e50..2299729a8d7 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -423,52 +423,7 @@ function buildElement(p: Panel): [PanelKind | LibraryPanelKind, string] { return [panelKind, element_identifier]; } else { // PanelKind - - const queries = getPanelQueries( - (p.targets as unknown as DataQuery[]) || [], - p.datasource || getDefaultDatasource() - ); - - const transformations = getPanelTransformations(p.transformations || []); - - const panelKind: PanelKind = { - kind: 'Panel', - spec: { - title: p.title || '', - description: p.description || '', - vizConfig: { - kind: p.type, - spec: { - fieldConfig: (p.fieldConfig as any) || defaultFieldConfigSource(), - options: p.options as any, - pluginVersion: p.pluginVersion!, - }, - }, - links: - p.links?.map((l) => ({ - title: l.title, - url: l.url || '', - targetBlank: l.targetBlank, - })) || [], - id: p.id!, - data: { - kind: 'QueryGroup', - spec: { - queries, - transformations, - queryOptions: { - cacheTimeout: p.cacheTimeout, - maxDataPoints: p.maxDataPoints, - interval: p.interval, - hideTimeOverride: p.hideTimeOverride, - queryCachingTTL: p.queryCachingTTL, - timeFrom: p.timeFrom, - timeShift: p.timeShift, - }, - }, - }, - }, - }; + const panelKind = buildPanelKind(p); return [panelKind, element_identifier]; } } @@ -515,6 +470,52 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc }); } +export function buildPanelKind(p: Panel): PanelKind { + const queries = getPanelQueries((p.targets as unknown as DataQuery[]) || [], p.datasource || getDefaultDatasource()); + + const transformations = getPanelTransformations(p.transformations || []); + + const panelKind: PanelKind = { + kind: 'Panel', + spec: { + title: p.title || '', + description: p.description || '', + vizConfig: { + kind: p.type, + spec: { + fieldConfig: (p.fieldConfig as any) || defaultFieldConfigSource(), + options: p.options as any, + pluginVersion: p.pluginVersion!, + }, + }, + links: + p.links?.map((l) => ({ + title: l.title, + url: l.url || '', + targetBlank: l.targetBlank, + })) || [], + id: p.id!, + data: { + kind: 'QueryGroup', + spec: { + queries, + transformations, + queryOptions: { + cacheTimeout: p.cacheTimeout, + maxDataPoints: p.maxDataPoints, + interval: p.interval, + hideTimeOverride: p.hideTimeOverride, + queryCachingTTL: p.queryCachingTTL, + timeFrom: p.timeFrom, + timeShift: p.timeShift, + }, + }, + }, + }, + }; + return panelKind; +} + function getPanelTransformations(transformations: DataTransformerConfig[]): TransformationKind[] { return transformations.map((t) => { return { diff --git a/public/app/features/dashboard/api/publicDashboardApi.ts b/public/app/features/dashboard/api/publicDashboardApi.ts index f7b19ace849..99fa565a453 100644 --- a/public/app/features/dashboard/api/publicDashboardApi.ts +++ b/public/app/features/dashboard/api/publicDashboardApi.ts @@ -43,7 +43,7 @@ export const publicDashboardApi = createApi({ try { await queryFulfilled; } catch (e) { - if (isFetchBaseQueryError(e) && isFetchError(e.error)) { + if (isFetchBaseQueryError(e) && isFetchError(e.error) && config.publicDashboardsEnabled) { dispatch(notifyApp(createErrorNotification(e.error.data.message))); } } diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx index c9ff8e91ea8..b512ee41b1b 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { uniqueId } from 'lodash'; import { ReactNode } from 'react'; import * as React from 'react'; import Highlighter from 'react-highlight-words'; @@ -31,11 +32,17 @@ export interface OptionsPaneItemInfo { */ export class OptionsPaneItemDescriptor { parent!: OptionsPaneCategoryDescriptor; + props: OptionsPaneItemInfo; - constructor(public props: OptionsPaneItemInfo) {} + constructor(props: OptionsPaneItemInfo) { + this.props = { ...props, id: props.id ?? props.title }; + if (this.props.id === '') { + this.props.id = uniqueId(); + } + } render(searchQuery?: string) { - return ; + return ; } useShowIf() { diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx index a05b51053eb..23a94ffe8cb 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx @@ -54,7 +54,8 @@ export function TransformationPicker(props: TransformationPickerProps) {
- It can help to switch to the Table visualization to understand what a transformation is doing.{' '} + It can help to switch to the Table visualization to understand what a transformation is + doing.{' '}

+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} + { + // click handling logic copied from react router: + // https://github.com/remix-run/react-router/blob/997b4d67e506d39ac6571cb369d6d2d6b3dda557/packages/react-router-dom/index.tsx#L392-L394s + if ( + focusSpanLink.onClick && + e.button === 0 && // Ignore everything but left clicks + (!e.currentTarget.target || e.currentTarget.target === '_self') && // Let browser handle "target=_blank" etc. + !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) // Ignore clicks with modifier keys + ) { + e.preventDefault(); + focusSpanLink.onClick(e); + } + }} + > + + + + ); +} diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx index 330b043ac82..dd1a23c12e8 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx @@ -250,7 +250,7 @@ describe('', () => { it('renders deep link URL', () => { render(); - expect(screen.getByText('test-spanID')).toBeInTheDocument(); + expect(screen.getByTestId('share-span-button')).toBeInTheDocument(); }); it('renders the flame graph', async () => { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index b9735b03720..67a6043f397 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -14,7 +14,6 @@ import { css } from '@emotion/css'; import { SpanStatusCode } from '@opentelemetry/api'; -import cx from 'classnames'; import { useCallback, useMemo } from 'react'; import { @@ -34,7 +33,7 @@ import { Trans, t } from '@grafana/i18n'; import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { usePluginLinks } from '@grafana/runtime'; import { TimeZone } from '@grafana/schema'; -import { Icon, TextArea, useStyles2 } from '@grafana/ui'; +import { TextArea, useStyles2 } from '@grafana/ui'; import { pyroscopeProfileIdTagKey } from '../../../createSpanLink'; import { autoColor } from '../../Theme'; @@ -49,6 +48,7 @@ import AccordianLogs from './AccordianLogs'; import AccordianReferences from './AccordianReferences'; import AccordianText from './AccordianText'; import DetailState from './DetailState'; +import { ShareSpanButton } from './ShareSpanButton'; import { getSpanDetailLinkButtons } from './SpanDetailLinkButtons'; import SpanFlameGraph from './SpanFlameGraph'; @@ -101,43 +101,22 @@ const getStyles = (theme: GrafanaTheme2) => { }), listWrapper: css({ overflow: 'hidden', + flexGrow: 1, + display: 'flex', + justifyContent: 'flex-end', }), list: css({ - textAlign: 'right', + textAlign: 'left', }), operationName: css({ margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', - flexBasis: '50%', + maxWidth: '50%', flexGrow: 0, flexShrink: 0, }), - debugInfo: css({ - label: 'debugInfo', - display: 'block', - letterSpacing: '0.25px', - margin: '0.5em 0 -0.75em', - textAlign: 'right', - }), - debugLabel: css({ - label: 'debugLabel', - '&::before': { - color: autoColor(theme, '#bbb'), - content: 'attr(data-label)', - }, - }), - debugValue: css({ - label: 'debugValue', - backgroundColor: 'inherit', - border: 'none', - color: autoColor(theme, '#888'), - cursor: 'pointer', - '&:hover': { - color: autoColor(theme, '#333'), - }, - }), AccordianWarnings: css({ label: 'AccordianWarnings', background: autoColor(theme, '#fafafa'), @@ -164,9 +143,6 @@ const getStyles = (theme: GrafanaTheme2) => { wordBreak: 'break-all', whiteSpace: 'pre', }), - LinkIcon: css({ - fontSize: '1.5em', - }), linkList: css({ display: 'flex', flexWrap: 'wrap', @@ -355,12 +331,13 @@ export default function SpanDetail(props: SpanDetailProps) { return (
-

+

{operationName} -
+
+
{linksComponent}
@@ -455,29 +432,6 @@ export default function SpanDetail(props: SpanDetailProps) { traceName={traceName} /> )} - - {/* TODO: fix keyboard a11y */} - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} - { - // click handling logic copied from react router: - // https://github.com/remix-run/react-router/blob/997b4d67e506d39ac6571cb369d6d2d6b3dda557/packages/react-router-dom/index.tsx#L392-L394s - if ( - focusSpanLink.onClick && - e.button === 0 && // Ignore everything but left clicks - (!e.currentTarget.target || e.currentTarget.target === '_self') && // Let browser handle "target=_blank" etc. - !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) // Ignore clicks with modifier keys - ) { - e.preventDefault(); - focusSpanLink.onClick(e); - } - }} - > - - - {spanID} -
); diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index 7574434861c..4f80a23f5ea 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -200,7 +200,7 @@ describe('createSpanLinkFactory', () => { datasource: 'loki1_uid', queries: [ { - expr: '{cluster="cluster1", hostname="hostname1", service_namespace="namespace1"} | label_format log_line_contains_trace_id=`{{ contains "7946b05c2e2e4e5a" __line__ }}` | log_line_contains_trace_id="true" OR trace_id="7946b05c2e2e4e5a" | label_format log_line_contains_span_id=`{{ contains "6605c7b08e715d6c" __line__ }}` | log_line_contains_span_id="true" OR span_id="6605c7b08e715d6c"', + expr: '{cluster="cluster1", hostname="hostname1", service_namespace="namespace1"} | label_format log_line_contains_trace_id=`{{ contains "7946b05c2e2e4e5a" __line__ }}` | log_line_contains_trace_id="true" or trace_id="7946b05c2e2e4e5a" | label_format log_line_contains_span_id=`{{ contains "6605c7b08e715d6c" __line__ }}` | log_line_contains_span_id="true" or span_id="6605c7b08e715d6c"', refId: '', }, ], diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 68b2dba6485..09376d61fd4 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -424,11 +424,11 @@ function getQueryForLoki( let expr = '{${__tags}}'; if (filterByTraceID && span.traceID) { expr += - ' | label_format log_line_contains_trace_id=`{{ contains "${__span.traceId}" __line__ }}` | log_line_contains_trace_id="true" OR trace_id="${__span.traceId}"'; + ' | label_format log_line_contains_trace_id=`{{ contains "${__span.traceId}" __line__ }}` | log_line_contains_trace_id="true" or trace_id="${__span.traceId}"'; } if (filterBySpanID && span.spanID) { expr += - ' | label_format log_line_contains_span_id=`{{ contains "${__span.spanId}" __line__ }}` | log_line_contains_span_id="true" OR span_id="${__span.spanId}"'; + ' | label_format log_line_contains_span_id=`{{ contains "${__span.spanId}" __line__ }}` | log_line_contains_span_id="true" or span_id="${__span.spanId}"'; } return { diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index 9835b9f3e09..c9fc652279f 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -43,6 +43,7 @@ export interface ChangeSizePayload { exploreId: string; width: number; } + export const changeSizeAction = createAction('explore/changeSize'); /** @@ -52,7 +53,9 @@ interface ChangePanelsState { exploreId: string; panelsState: ExplorePanelsState; } + export const changePanelsStateAction = createAction('explore/changePanels'); + export function changePanelState( exploreId: string, panel: PreferredVisualisationType, @@ -83,6 +86,7 @@ interface ChangeCorrelationHelperData { exploreId: string; correlationEditorHelperData?: ExploreCorrelationHelperData; } + export const changeCorrelationHelperData = createAction( 'explore/changeCorrelationHelperData' ); @@ -99,17 +103,20 @@ interface InitializeExplorePayload { datasourceInstance?: DataSourceApi; eventBridge: EventBusExtended; } + const initializeExploreAction = createAction('explore/initializeExploreAction'); export interface SetUrlReplacedPayload { exploreId: string; } + export const setUrlReplacedAction = createAction('explore/setUrlReplaced'); export interface SaveCorrelationsPayload { exploreId: string; correlations: CorrelationData[]; } + export const saveCorrelationsAction = createAction('explore/saveCorrelationsAction'); /** @@ -130,6 +137,7 @@ export interface InitializeExploreOptions { position?: number; eventBridge: EventBusExtended; } + /** * Initialize Explore state with state from the URL and the React component. * Call this only on components for with the Explore state has not been initialized. @@ -214,7 +222,7 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac state = timeReducer(state, action); if (changeSizeAction.match(action)) { - const containerWidth = action.payload.width; + const containerWidth = Math.floor(action.payload.width); return { ...state, containerWidth }; } diff --git a/public/app/features/expressions/ExpressionDatasource.ts b/public/app/features/expressions/ExpressionDatasource.ts index 57f4b45ffbf..ded8c75d89f 100644 --- a/public/app/features/expressions/ExpressionDatasource.ts +++ b/public/app/features/expressions/ExpressionDatasource.ts @@ -1,15 +1,27 @@ -import { from, mergeMap, Observable } from 'rxjs'; +import { from, lastValueFrom, map, mergeMap, Observable } from 'rxjs'; import { + DataFrame, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings, DataSourcePluginMeta, PluginType, ScopedVars, + TimeRange, } from '@grafana/data'; -import { DataSourceWithBackend, getDataSourceSrv, getTemplateSrv } from '@grafana/runtime'; +import { SQLQuery } from '@grafana/plugin-ui'; +import { + BackendDataSourceResponse, + DataSourceWithBackend, + FetchResponse, + getBackendSrv, + getDataSourceSrv, + getTemplateSrv, + toDataQueryResponse, +} from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/internal'; +import { DataQuery } from '@grafana/schema/dist/esm/index'; import icnDatasourceSvg from 'img/icn-datasource.svg'; import { ExpressionQueryEditor } from './ExpressionQueryEditor'; @@ -59,6 +71,38 @@ export class ExpressionDatasourceApi extends DataSourceWithBackend, range: TimeRange, queries: DataQuery[]): Promise { + const refId = request.refId || 'meta'; + const metaSqlExpressionQuery: ExpressionQuery = { + window: '', + hide: false, + expression: request.rawSql, + datasource: ExpressionDatasourceRef, + refId, + type: ExpressionQueryType.sql, + }; + return lastValueFrom( + getBackendSrv() + .fetch({ + url: '/api/ds/query', + method: 'POST', + headers: this.getRequestHeaders(), + data: { + from: range.from.valueOf().toString(), + to: range.to.valueOf().toString(), + queries: [...queries, metaSqlExpressionQuery], + }, + requestId: refId, + }) + .pipe( + map((res: FetchResponse) => { + const rsp = toDataQueryResponse(res, queries); + return rsp.data[0] ?? { fields: [] }; + }) + ) + ); + } } export const instanceSettings: DataSourceInstanceSettings = { diff --git a/public/app/features/expressions/ExpressionQueryEditor.tsx b/public/app/features/expressions/ExpressionQueryEditor.tsx index b1f07c253bb..e49d3932c48 100644 --- a/public/app/features/expressions/ExpressionQueryEditor.tsx +++ b/public/app/features/expressions/ExpressionQueryEditor.tsx @@ -118,7 +118,7 @@ export function ExpressionQueryEditor(props: Props) { return ; case ExpressionQueryType.sql: - return ; + return ; } }; diff --git a/public/app/features/expressions/components/SqlExpr.test.tsx b/public/app/features/expressions/components/SqlExpr.test.tsx index 566e180fabf..e63a80133d6 100644 --- a/public/app/features/expressions/components/SqlExpr.test.tsx +++ b/public/app/features/expressions/components/SqlExpr.test.tsx @@ -19,7 +19,7 @@ describe('SqlExpr', () => { const refIds = [{ value: 'A' }]; const query = { refId: 'expr1', type: 'sql', expression: '' } as ExpressionQuery; - render(); + render(); // Verify onChange was called expect(onChange).toHaveBeenCalled(); @@ -35,7 +35,7 @@ describe('SqlExpr', () => { const existingExpression = 'SELECT 1 AS foo'; const query = { refId: 'expr1', type: 'sql', expression: existingExpression } as ExpressionQuery; - render(); + render(); // Check if onChange was called if (onChange.mock.calls.length > 0) { @@ -53,7 +53,7 @@ describe('SqlExpr', () => { const refIds = [{ value: 'A' }]; const query = { refId: 'expr1', type: 'sql' } as ExpressionQuery; - render(); + render(); const updatedQuery = onChange.mock.calls[0][0]; expect(updatedQuery.format).toBe('alerting'); diff --git a/public/app/features/expressions/components/SqlExpr.tsx b/public/app/features/expressions/components/SqlExpr.tsx index 1f61a558471..ac2917b1c40 100644 --- a/public/app/features/expressions/components/SqlExpr.tsx +++ b/public/app/features/expressions/components/SqlExpr.tsx @@ -2,34 +2,44 @@ import { css } from '@emotion/css'; import { useMemo, useRef, useEffect, useState } from 'react'; import { SelectableValue } from '@grafana/data'; -import { SQLEditor, LanguageDefinition } from '@grafana/plugin-ui'; +import { SQLEditor, CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/plugin-ui'; +import { DataQuery } from '@grafana/schema/dist/esm/index'; import { useStyles2 } from '@grafana/ui'; import { SqlExpressionQuery } from '../types'; +import { fetchSQLFields } from '../utils/metaSqlExpr'; + +import { getSqlCompletionProvider } from './sqlCompletionProvider'; // Account for Monaco editor's border to prevent clipping const EDITOR_BORDER_ADJUSTMENT = 2; // 1px border on top and bottom -// Define the language definition for MySQL syntax highlighting and autocomplete -const EDITOR_LANGUAGE_DEFINITION: LanguageDefinition = { - id: 'mysql', - // Additional properties could be added here in the future if needed - // eg: - // completionProvider: to autocomplete field (ie column) names when given - // a table name (dataframe reference) - // formatter: to format the SQL query and dashboard variables -}; - interface Props { refIds: Array>; query: SqlExpressionQuery; + queries: DataQuery[] | undefined; onChange: (query: SqlExpressionQuery) => void; /** Should the `format` property be set to `alerting`? */ alerting?: boolean; } -export const SqlExpr = ({ onChange, refIds, query, alerting = false }: Props) => { +export const SqlExpr = ({ onChange, refIds, query, alerting = false, queries }: Props) => { const vars = useMemo(() => refIds.map((v) => v.value!), [refIds]); + const completionProvider = useMemo( + () => + getSqlCompletionProvider({ + getFields: (identifier: TableIdentifier) => fetchFields(identifier, queries || []), + refIds, + }), + [queries, refIds] + ); + + // Define the language definition for MySQL syntax highlighting and autocomplete + const EDITOR_LANGUAGE_DEFINITION: LanguageDefinition = { + id: 'mysql', + completionProvider, + }; + const initialQuery = `SELECT * FROM ${vars[0]} LIMIT 10`; @@ -90,3 +100,8 @@ const getStyles = () => ({ minHeight: '100px', }), }); + +async function fetchFields(identifier: TableIdentifier, queries: DataQuery[]) { + const fields = await fetchSQLFields({ table: identifier.table }, queries); + return fields.map((t) => ({ name: t.name, completion: t.value, kind: CompletionItemKind.Field })); +} diff --git a/public/app/features/expressions/components/sqlCompletionProvider.ts b/public/app/features/expressions/components/sqlCompletionProvider.ts new file mode 100644 index 00000000000..013088aa770 --- /dev/null +++ b/public/app/features/expressions/components/sqlCompletionProvider.ts @@ -0,0 +1,46 @@ +import { SelectableValue } from '@grafana/data'; +import { ColumnDefinition, LanguageCompletionProvider, TableDefinition, TableIdentifier } from '@grafana/plugin-ui'; +import { config } from '@grafana/runtime'; + +import { ALLOWED_FUNCTIONS } from '../utils/metaSqlExpr'; + +interface CompletionProviderGetterArgs { + getFields: (t: TableIdentifier) => Promise; + refIds: Array>; +} + +export const getSqlCompletionProvider: (args: CompletionProviderGetterArgs) => LanguageCompletionProvider = + (args) => (monaco, language) => ({ + ...language, + triggerCharacters: [' '], + tables: { + resolve: async () => { + const refIdsToTableDefs = args.refIds.map((refId) => { + const tableDef: TableDefinition = { + name: refId.label || refId.value || '', + completion: refId.label || refId.value || '', + }; + return tableDef; + }); + return refIdsToTableDefs; + }, + }, + columns: { + resolve: async (t?: TableIdentifier) => { + if (config.featureToggles.sqlExpressionsColumnAutoComplete) { + try { + return await args.getFields({ table: t?.table }); + } catch { + return []; + } + } else { + return []; + } + }, + }, + supportedFunctions: () => { + return ALLOWED_FUNCTIONS.map((func) => { + return { id: func, name: func }; + }); + }, + }); diff --git a/public/app/features/expressions/utils/metaSqlExpr.ts b/public/app/features/expressions/utils/metaSqlExpr.ts new file mode 100644 index 00000000000..349d2abb8a3 --- /dev/null +++ b/public/app/features/expressions/utils/metaSqlExpr.ts @@ -0,0 +1,155 @@ +import { v4 as uuidv4 } from 'uuid'; + +import { getDefaultTimeRange, DataFrameView } from '@grafana/data'; +import { QueryFormat, SQLQuery, SQLSelectableValue } from '@grafana/plugin-ui'; +import { DataQuery } from '@grafana/schema'; +import { mapFieldsToTypes } from 'app/plugins/datasource/mysql/fields'; +import { quoteIdentifierIfNecessary } from 'app/plugins/datasource/mysql/sqlUtil'; + +import { dataSource } from '../ExpressionDatasource'; + +export async function fetchSQLFields(query: Partial, queries: DataQuery[]): Promise { + const datasource = dataSource; + if (!query.table) { + return []; + } + + const queryString = `SELECT * FROM ${query.table} LIMIT 1`; + + const queryResponse = await datasource.runMetaSQLExprQuery( + { rawSql: queryString, format: QueryFormat.Table, refId: `fields-${uuidv4()}` }, + getDefaultTimeRange(), + queries.filter((q) => q.refId === query.table) + ); + const frame = new DataFrameView(queryResponse); + + const fields = Object.values(frame.fields).map(({ name, type }) => { + return { + name, + text: name, + label: name, + value: quoteIdentifierIfNecessary(name), + type, + }; + }); + + return mapFieldsToTypes(fields); +} + +// based off https://github.com/grafana/grafana/blob/main/pkg/expr/sql/parser_allow.go +export const ALLOWED_FUNCTIONS = [ + 'if', + 'coalesce', + 'ifnull', + 'nullif', + 'sum', + 'avg', + 'count', + 'min', + 'max', + 'stddev', + 'std', + 'stddev_pop', + 'variance', + 'var_pop', + 'group_concat', + 'row_number', + 'rank', + 'dense_rank', + 'lead', + 'lag', + 'first_value', + 'last_value', + 'abs', + 'round', + 'floor', + 'ceiling', + 'ceil', + 'sqrt', + 'pow', + 'power', + 'mod', + 'log', + 'log10', + 'exp', + 'sign', + 'ln', + 'truncate', + 'sin', + 'cos', + 'tan', + 'asin', + 'acos', + 'atan', + 'atan2', + 'rand', + 'pi', + 'concat', + 'length', + 'char_length', + 'lower', + 'upper', + 'substring', + 'substring_index', + 'left', + 'right', + 'ltrim', + 'rtrim', + 'replace', + 'reverse', + 'lcase', + 'ucase', + 'mid', + 'repeat', + 'position', + 'instr', + 'locate', + 'ascii', + 'ord', + 'char', + 'regexp_substr', + 'str_to_date', + 'date_format', + 'date_add', + 'date_sub', + 'year', + 'month', + 'day', + 'weekday', + 'datediff', + 'unix_timestamp', + 'from_unixtime', + 'extract', + 'hour', + 'minute', + 'second', + 'dayname', + 'monthname', + 'dayofweek', + 'dayofmonth', + 'dayofyear', + 'week', + 'quarter', + 'time_to_sec', + 'sec_to_time', + 'timestampdiff', + 'timestampadd', + 'cast', + 'convert', + 'json_extract', + 'json_object', + 'json_array', + 'json_merge_patch', + 'json_valid', + 'json_contains', + 'json_length', + 'json_type', + 'json_keys', + 'json_search', + 'json_quote', + 'json_unquote', + 'json_set', + 'json_insert', + 'json_replace', + 'json_remove', +]; diff --git a/public/app/features/logs/components/panel/LogLine.test.tsx b/public/app/features/logs/components/panel/LogLine.test.tsx index e31ba38cabf..89803a457bb 100644 --- a/public/app/features/logs/components/panel/LogLine.test.tsx +++ b/public/app/features/logs/components/panel/LogLine.test.tsx @@ -14,6 +14,11 @@ import { defaultProps, defaultValue } from './__mocks__/LogListContext'; import { LogListModel } from './processing'; import { LogLineVirtualization } from './virtualization'; +jest.mock('@grafana/assistant', () => ({ + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn(() => [true, jest.fn()]), +})); + jest.mock('./LogListContext'); jest.mock('../LogDetails'); diff --git a/public/app/features/logs/components/panel/LogLine.tsx b/public/app/features/logs/components/panel/LogLine.tsx index 7bc7edd9cd6..58abcb1b434 100644 --- a/public/app/features/logs/components/panel/LogLine.tsx +++ b/public/app/features/logs/components/panel/LogLine.tsx @@ -241,7 +241,7 @@ const LogLineComponent = memo( )} - {detailsMode === 'inline' && detailsShown && } + {detailsMode === 'inline' && detailsShown && } ); } @@ -323,7 +323,7 @@ const LogLineBody = ({ log, styles }: { log: LogListModel; styles: LogLineStyles const { matchingUids, search } = useLogListSearchContext(); const highlight = useMemo(() => { - const searchWords = log.searchWords && log.searchWords[0] ? log.searchWords.slice() : []; + const searchWords = syntaxHighlighting && log.searchWords && log.searchWords[0] ? log.searchWords.slice() : []; if (search && matchingUids?.includes(log.uid)) { searchWords.push(search); } @@ -331,7 +331,7 @@ const LogLineBody = ({ log, styles }: { log: LogListModel; styles: LogLineStyles return undefined; } return { searchWords, highlightClassName: styles.matchHighLight }; - }, [log.searchWords, log.uid, matchingUids, search, styles.matchHighLight]); + }, [log.searchWords, log.uid, matchingUids, search, styles.matchHighLight, syntaxHighlighting]); if (log.hasAnsi) { return ( @@ -376,7 +376,7 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali parsedField: theme.colors.text.primary, }; - const hoverColor = tinycolor(theme.colors.background.canvas).darken(4).toRgbString(); + const hoverColor = tinycolor(theme.colors.background.canvas).darken(5).toRgbString(); return { logLine: css({ @@ -450,7 +450,7 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali lineHeight: theme.typography.bodySmall.lineHeight, }), detailsDisplayed: css({ - background: hoverColor, + background: tinycolor(theme.colors.background.canvas).darken(2).toRgbString(), }), pinnedLogLine: css({ backgroundColor: tinycolor(theme.colors.info.transparent).setAlpha(0.25).toString(), diff --git a/public/app/features/logs/components/panel/LogLineDetails.test.tsx b/public/app/features/logs/components/panel/LogLineDetails.test.tsx index fd4e5be7af3..112ea2531ba 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.test.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.test.tsx @@ -23,6 +23,13 @@ import { LogLineDetails, Props } from './LogLineDetails'; import { LogListContext, LogListContextData } from './LogListContext'; import { defaultValue } from './__mocks__/LogListContext'; +jest.mock('@grafana/assistant', () => { + return { + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn().mockReturnValue([true, jest.fn()]), + }; +}); + jest.mock('@grafana/runtime', () => { return { ...jest.requireActual('@grafana/runtime'), @@ -515,4 +522,88 @@ describe('LogLineDetails', () => { expect(setDisplayedFields).toHaveBeenCalledWith(['key1', 'key3', 'key2']); }); }); + + describe('Multiple log details', () => { + test('Does not render tabs when displaying a single log', () => { + setup(undefined, { labels: { key1: 'label1', key2: 'label2' } }); + expect(screen.queryAllByRole('tab')).toHaveLength(0); + }); + + test('Renders multiple log details', async () => { + const logs = [ + createLogLine({ uid: '1', logLevel: LogLevel.error, timeEpochMs: 1546297200000, entry: 'First log' }), + createLogLine({ uid: '2', logLevel: LogLevel.error, timeEpochMs: 1546297200000, entry: 'Second log' }), + ]; + setup({ logs }, undefined, { showDetails: logs }); + + expect(screen.queryAllByRole('tab')).toHaveLength(2); + + await userEvent.click(screen.getByText('Log line')); + + expect(screen.getAllByText('First log')).toHaveLength(1); + expect(screen.getAllByText('Second log')).toHaveLength(2); + + await userEvent.click(screen.queryAllByRole('tab')[0]); + + expect(screen.getAllByText('First log')).toHaveLength(2); + expect(screen.getAllByText('Second log')).toHaveLength(1); + }); + + test('Changes details focus when logs are added and removed', async () => { + const logs = [ + createLogLine({ uid: '1', logLevel: LogLevel.error, timeEpochMs: 1546297200000, entry: 'First log' }), + createLogLine({ uid: '2', logLevel: LogLevel.error, timeEpochMs: 1546297200000, entry: 'Second log' }), + ]; + + const props: Props = { + containerElement: document.createElement('div'), + focusLogLine: jest.fn(), + logs: [logs[0]], + onResize: jest.fn(), + }; + + const contextData: LogListContextData = { + ...defaultValue, + showDetails: [logs[0]], + }; + + const { rerender } = render( + + + + ); + + expect(screen.queryAllByRole('tab')).toHaveLength(0); + + await userEvent.click(screen.getByText('Log line')); + // Tab not displayed, only line body + expect(screen.getAllByText('First log')).toHaveLength(1); + + contextData.showDetails = logs; + props.logs = logs; + + rerender( + + + + ); + + expect(screen.queryAllByRole('tab')).toHaveLength(2); + // Tab and log line body + expect(screen.getAllByText('Second log')).toHaveLength(2); + + contextData.showDetails = [logs[1]]; + props.logs = [logs[1]]; + + rerender( + + + + ); + + expect(screen.queryAllByRole('tab')).toHaveLength(0); + // Tab not displayed, only line body + expect(screen.getAllByText('Second log')).toHaveLength(1); + }); + }); }); diff --git a/public/app/features/logs/components/panel/LogLineDetails.tsx b/public/app/features/logs/components/panel/LogLineDetails.tsx index fee8143396f..7a4e5a2e425 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.tsx @@ -1,9 +1,12 @@ import { css } from '@emotion/css'; import { Resizable } from 're-resizable'; -import { memo, useCallback, useEffect, useRef } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { usePrevious } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { getDragStyles, useStyles2 } from '@grafana/ui'; +import { t } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; +import { getDragStyles, Icon, Tab, TabsBar, useStyles2 } from '@grafana/ui'; import { LogLineDetailsComponent } from './LogLineDetailsComponent'; import { getDetailsScrollPosition, saveDetailsScrollPosition, useLogListContext } from './LogListContext'; @@ -19,18 +22,12 @@ export interface Props { export type LogLineDetailsMode = 'inline' | 'sidebar'; -export const LogLineDetails = ({ containerElement, focusLogLine, logs, onResize }: Props) => { - const { detailsWidth, setDetailsWidth, showDetails } = useLogListContext(); +export const LogLineDetails = memo(({ containerElement, focusLogLine, logs, onResize }: Props) => { + const { detailsWidth, noInteractions, setDetailsWidth } = useLogListContext(); const styles = useStyles2(getStyles, 'sidebar'); const dragStyles = useStyles2(getDragStyles); const containerRef = useRef(null); - useEffect(() => { - focusLogLine(showDetails[0]); - // Just once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const handleResize = useCallback(() => { if (containerRef.current) { setDetailsWidth(containerRef.current.clientWidth); @@ -38,15 +35,20 @@ export const LogLineDetails = ({ containerElement, focusLogLine, logs, onResize onResize(); }, [onResize, setDetailsWidth]); - const maxWidth = containerElement.clientWidth - LOG_LIST_MIN_WIDTH; + const reportResize = useCallback(() => { + if (containerRef.current && !noInteractions) { + reportInteraction('logs_log_line_details_sidebar_resized', { + width: Math.round(containerRef.current.clientWidth), + }); + } + }, [noInteractions]); - if (!showDetails.length) { - return null; - } + const maxWidth = containerElement.clientWidth - LOG_LIST_MIN_WIDTH; return (
-
- -
+
); -}; +}); +LogLineDetails.displayName = 'LogLineDetails'; + +const LogLineDetailsTabs = memo(({ focusLogLine, logs }: Pick) => { + const { closeDetails, noInteractions, showDetails, toggleDetails } = useLogListContext(); + const [currentLog, setCurrentLog] = useState(showDetails[0]); + const previousShowDetails = usePrevious(showDetails); + const styles = useStyles2(getStyles, 'sidebar'); + + useEffect(() => { + focusLogLine(currentLog); + if (!noInteractions) { + reportInteraction('logs_log_line_details_displayed', { + mode: 'sidebar', + }); + } + // Once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!showDetails.length) { + closeDetails(); + return; + } + // Focus on the recently open + if (!previousShowDetails || showDetails.length > previousShowDetails.length) { + setCurrentLog(showDetails[showDetails.length - 1]); + return; + } else if (!showDetails.find((log) => log.uid === currentLog.uid)) { + setCurrentLog(showDetails[showDetails.length - 1]); + } + }, [closeDetails, currentLog.uid, previousShowDetails, showDetails]); + + return ( + <> + {showDetails.length > 1 && ( + + {showDetails.map((log) => { + return ( + setCurrentLog(log)} + suffix={() => ( + toggleDetails(log)} + /> + )} + /> + ); + })} + + )} +
+ +
+ + ); +}); +LogLineDetailsTabs.displayName = 'LogLineDetailsTabs'; export interface InlineLogLineDetailsProps { + log: LogListModel; logs: LogListModel[]; } -export const InlineLogLineDetails = memo(({ logs }: InlineLogLineDetailsProps) => { - const { showDetails } = useLogListContext(); +export const InlineLogLineDetails = memo(({ logs, log }: InlineLogLineDetailsProps) => { + const { noInteractions } = useLogListContext(); const styles = useStyles2(getStyles, 'inline'); const scrollRef = useRef(null); + useEffect(() => { + if (!noInteractions) { + reportInteraction('logs_log_line_details_displayed', { + mode: 'inline', + }); + } + }, [noInteractions]); + const saveScroll = useCallback(() => { - saveDetailsScrollPosition(showDetails[0], scrollRef.current?.scrollTop ?? 0); - }, [showDetails]); + saveDetailsScrollPosition(log, scrollRef.current?.scrollTop ?? 0); + }, [log]); useEffect(() => { if (!scrollRef.current) { return; } - scrollRef.current.scrollTop = getDetailsScrollPosition(showDetails[0]); - }, [showDetails]); - - if (!showDetails.length) { - return null; - } + scrollRef.current.scrollTop = getDetailsScrollPosition(log); + }, [log]); return (
- +
diff --git a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx index d38eff282f9..96429ce4327 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx @@ -4,6 +4,7 @@ import { memo, startTransition, useCallback, useMemo, useRef, useState } from 'r import { DataFrameType, GrafanaTheme2, store } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; import { ControlledCollapse, useStyles2 } from '@grafana/ui'; import { getLabelTypeFromRow } from '../../utils'; @@ -18,12 +19,13 @@ import { useLogListContext } from './LogListContext'; import { LogListModel } from './processing'; interface LogLineDetailsComponentProps { + focusLogLine?: (log: LogListModel) => void; log: LogListModel; logs: LogListModel[]; } -export const LogLineDetailsComponent = memo(({ log, logs }: LogLineDetailsComponentProps) => { - const { displayedFields, logOptionsStorageKey, setDisplayedFields } = useLogListContext(); +export const LogLineDetailsComponent = memo(({ focusLogLine, log, logs }: LogLineDetailsComponentProps) => { + const { displayedFields, noInteractions, logOptionsStorageKey, setDisplayedFields } = useLogListContext(); const [search, setSearch] = useState(''); const inputRef = useRef(''); const styles = useStyles2(getStyles); @@ -75,8 +77,14 @@ export const LogLineDetailsComponent = memo(({ log, logs }: LogLineDetailsCompon const handleToggle = useCallback( (option: string, isOpen: boolean) => { store.set(`${logOptionsStorageKey}.log-details.${option}`, isOpen); + if (!noInteractions) { + reportInteraction('logs_log_line_details_section_toggled', { + section: option.replace('Open', ''), + state: isOpen ? 'open' : 'closed', + }); + } }, - [logOptionsStorageKey] + [logOptionsStorageKey, noInteractions] ); const handleSearch = useCallback((newSearch: string) => { @@ -94,7 +102,7 @@ export const LogLineDetailsComponent = memo(({ log, logs }: LogLineDetailsCompon return ( <> - +
) => { + if (noInteractions) { + return; + } + reportInteraction(interactionName, properties); + }, + [noInteractions] + ); + const showField = useCallback(() => { if (onClickShowField) { onClickShowField(keys[0]); } - reportInteraction('grafana_explore_logs_log_details_replace_line_clicked', { + reportInteractionWrapper('logs_log_line_details_show_field_clicked', { datasourceType: log.datasourceType, - logRowUid: log.uid, - type: 'enable', }); - }, [onClickShowField, keys, log.datasourceType, log.uid]); + }, [onClickShowField, reportInteractionWrapper, log.datasourceType, keys]); const hideField = useCallback(() => { if (onClickHideField) { onClickHideField(keys[0]); } - reportInteraction('grafana_explore_logs_log_details_replace_line_clicked', { + reportInteractionWrapper('logs_log_line_details_hide_field_clicked', { datasourceType: log.datasourceType, - logRowUid: log.uid, - type: 'disable', }); - }, [onClickHideField, keys, log.datasourceType, log.uid]); + }, [onClickHideField, reportInteractionWrapper, log.datasourceType, keys]); const filterLabel = useCallback(() => { if (onClickFilterLabel) { onClickFilterLabel(keys[0], values[0], logRowToSingleRowDataFrame(log) || undefined); } - reportInteraction('grafana_explore_logs_log_details_filter_clicked', { + reportInteractionWrapper('logs_log_line_details_filter_clicked', { datasourceType: log.datasourceType, filterType: 'include', logRowUid: log.uid, }); - }, [onClickFilterLabel, keys, values, log]); + }, [onClickFilterLabel, reportInteractionWrapper, log, keys, values]); const filterOutLabel = useCallback(() => { if (onClickFilterOutLabel) { onClickFilterOutLabel(keys[0], values[0], logRowToSingleRowDataFrame(log) || undefined); } - reportInteraction('grafana_explore_logs_log_details_filter_clicked', { + reportInteractionWrapper('logs_log_line_details_filter_clicked', { datasourceType: log.datasourceType, filterType: 'exclude', logRowUid: log.uid, }); - }, [onClickFilterOutLabel, keys, values, log]); + }, [onClickFilterOutLabel, reportInteractionWrapper, log, keys, values]); const labelFilterActive = useCallback(async () => { if (isLabelFilterActive) { @@ -237,14 +244,14 @@ export const LogLineDetailsField = ({ const showStats = useCallback(() => { setShowFieldStats((showFieldStats: boolean) => !showFieldStats); - reportInteraction('grafana_explore_logs_log_details_stats_clicked', { + reportInteractionWrapper('logs_log_line_details_stats_clicked', { dataSourceType: log.datasourceType, - fieldType: isLabel ? 'label' : 'detectedField', + fieldType: isLabel ? 'label' : 'field', type: showFieldsStats ? 'close' : 'open', logRowUid: log.uid, app, }); - }, [app, isLabel, log.datasourceType, log.uid, showFieldsStats]); + }, [app, isLabel, log.datasourceType, log.uid, reportInteractionWrapper, showFieldsStats]); const refIdTooltip = useMemo( () => (app === CoreApp.Explore && log.dataFrame?.refId ? ` in query ${log.dataFrame?.refId}` : ''), diff --git a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx index 140a7f86fca..9a4e6eedce0 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx @@ -3,6 +3,7 @@ import { useCallback, useMemo, MouseEvent, useRef, ChangeEvent } from 'react'; import { colorManipulator, GrafanaTheme2, LogRowModel, store } from '@grafana/data'; import { t } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; import { IconButton, Input, useStyles2 } from '@grafana/ui'; import { copyText, handleOpenLogsContextClick } from '../../utils'; @@ -13,12 +14,13 @@ import { useLogIsPinned, useLogListContext } from './LogListContext'; import { LogListModel } from './processing'; interface Props { + focusLogLine?: (log: LogListModel) => void; log: LogListModel; search: string; onSearch(newSearch: string): void; } -export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { +export const LogLineDetailsHeader = ({ focusLogLine, log, search, onSearch }: Props) => { const { closeDetails, detailsMode, @@ -26,6 +28,7 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { getRowContextQuery, logOptionsStorageKey, logSupportsContext, + noInteractions, setDetailsMode, onClickHideField, onClickShowField, @@ -34,19 +37,38 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { onPinLine, onUnpinLine, wrapLogMessage, + isAssistantAvailable, + openAssistantByLog, } = useLogListContext(); const pinned = useLogIsPinned(log); const styles = useStyles2(getStyles, detailsMode, wrapLogMessage); const containerRef = useRef(null); const inputRef = useRef(null); + const searchUsedRef = useRef(false); + + const reportInteractionWrapper = useCallback( + (interactionName: string, properties?: Record) => { + if (noInteractions) { + return; + } + reportInteraction(interactionName, properties); + }, + [noInteractions] + ); + + const scrollToLogLine = useCallback(() => { + focusLogLine?.(log); + }, [focusLogLine, log]); const copyLogLine = useCallback(() => { copyText(log.entry, containerRef); - }, [log.entry]); + reportInteractionWrapper('logs_log_line_details_header_copy_clicked'); + }, [log.entry, reportInteractionWrapper]); const copyLinkToLogLine = useCallback(() => { onPermalinkClick?.(log); - }, [log, onPermalinkClick]); + reportInteractionWrapper('logs_log_line_details_header_permalink_clicked'); + }, [log, onPermalinkClick, reportInteractionWrapper]); const togglePinning = useCallback(() => { if (pinned) { @@ -54,7 +76,8 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { } else { onPinLine?.(log); } - }, [log, onPinLine, onUnpinLine, pinned]); + reportInteractionWrapper('logs_log_line_details_header_pinning_clicked'); + }, [log, onPinLine, onUnpinLine, pinned, reportInteractionWrapper]); const shouldlogSupportsContext = useMemo( () => (logSupportsContext ? logSupportsContext(log) : false), @@ -64,8 +87,9 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { const showContext = useCallback( async (event: MouseEvent) => { handleOpenLogsContextClick(event, log, getRowContextQuery, (log: LogRowModel) => onOpenContext?.(log, () => {})); + reportInteractionWrapper('logs_log_line_details_header_context_clicked'); }, - [onOpenContext, getRowContextQuery, log] + [log, getRowContextQuery, reportInteractionWrapper, onOpenContext] ); const showLogLineToggle = onClickHideField && onClickShowField && displayedFields.length > 0; @@ -86,7 +110,8 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { } else { onClickShowField?.(LOG_LINE_BODY_FIELD_NAME); } - }, [logLineDisplayed, onClickHideField, onClickShowField]); + reportInteractionWrapper('logs_log_line_details_header_show_logline_clicked'); + }, [logLineDisplayed, onClickHideField, onClickShowField, reportInteractionWrapper]); const clearSearch = useMemo( () => ( @@ -95,6 +120,7 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { size="sm" onClick={() => { onSearch(''); + reportInteractionWrapper('logs_log_line_details_header_search_cleared'); if (inputRef.current) { inputRef.current.value = ''; } @@ -102,14 +128,18 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { tooltip={t('logs.log-line-details.clear-search', 'Clear')} /> ), - [onSearch] + [onSearch, reportInteractionWrapper] ); const handleSearch = useCallback( (e: ChangeEvent) => { onSearch(e.target.value); + if (!searchUsedRef.current) { + reportInteractionWrapper('logs_log_line_details_header_search_used'); + searchUsedRef.current = true; + } }, - [onSearch] + [onSearch, reportInteractionWrapper] ); return ( @@ -120,22 +150,42 @@ export const LogLineDetailsHeader = ({ log, search, onSearch }: Props) => { ref={inputRef} suffix={search !== '' ? clearSearch : undefined} /> - {showLogLineToggle && ( - - )}
+ {isAssistantAvailable && ( + openAssistantByLog?.(log)} + tabIndex={0} + /> + )} + {focusLogLine && ( + + )} + {showLogLineToggle && ( + + )} ({ + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn(() => [true, jest.fn()]), +})); + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + isAssistantAvailable: true, +})); + const theme = createTheme(); const styles = getStyles(theme); const contextProps = { diff --git a/public/app/features/logs/components/panel/LogLineMenu.tsx b/public/app/features/logs/components/panel/LogLineMenu.tsx index 37a0df589d6..49b55b368d1 100644 --- a/public/app/features/logs/components/panel/LogLineMenu.tsx +++ b/public/app/features/logs/components/panel/LogLineMenu.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useRef, MouseEvent } from 'react'; +import { MouseEvent, useCallback, useMemo, useRef } from 'react'; import { LogRowContextOptions, LogRowModel } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -45,6 +45,8 @@ export const LogLineMenu = ({ log, styles }: Props) => { logLineMenuCustomItems = [], logSupportsContext, toggleDetails, + isAssistantAvailable, + openAssistantByLog, } = useLogListContext(); const pinned = useLogIsPinned(log); const menuRef = useRef(null); @@ -108,6 +110,13 @@ export const LogLineMenu = ({ log, styles }: Props) => { {onPermalinkClick && log.rowId !== undefined && log.uid && ( )} + {isAssistantAvailable && ( + openAssistantByLog?.(log)} + icon="ai-sparkle" + label={t('logs.log-line-menu.open-assistant', 'Explain this log line in Assistant')} + /> + )} {logLineMenuCustomItems.map((item, i) => { if (isDivider(item)) { return ; @@ -134,6 +143,8 @@ export const LogLineMenu = ({ log, styles }: Props) => { showContext, toggleLogDetails, togglePinning, + isAssistantAvailable, + openAssistantByLog, ] ); diff --git a/public/app/features/logs/components/panel/LogList.test.tsx b/public/app/features/logs/components/panel/LogList.test.tsx index fde0ea89dec..4f740d8059b 100644 --- a/public/app/features/logs/components/panel/LogList.test.tsx +++ b/public/app/features/logs/components/panel/LogList.test.tsx @@ -2,16 +2,23 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CoreApp, getDefaultTimeRange, LogRowModel, LogsDedupStrategy, LogsSortOrder, store } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../../utils'; import { createLogRow } from '../mocks/logRow'; import { LogList, Props } from './LogList'; +jest.mock('@grafana/assistant', () => ({ + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn(() => [true, jest.fn()]), +})); + jest.mock('@grafana/runtime', () => { return { ...jest.requireActual('@grafana/runtime'), usePluginLinks: jest.fn().mockReturnValue({ links: [] }), + reportInteraction: jest.fn(), config: { ...jest.requireActual('@grafana/runtime').config, featureToggles: { @@ -338,4 +345,20 @@ describe('LogList', () => { expect(screen.getByText('some text')).toBeInTheDocument(); }); }); + describe('Interactions', () => { + beforeEach(() => { + sessionStorage.clear(); + jest.mocked(reportInteraction).mockClear(); + }); + test('Reports interactions ', async () => { + render(); + await screen.findByText('log message 1'); + expect(reportInteraction).toHaveBeenCalled(); + }); + test('Can disable interaction report ', async () => { + render(); + await screen.findByText('log message 1'); + expect(reportInteraction).not.toHaveBeenCalled(); + }); + }); }); diff --git a/public/app/features/logs/components/panel/LogList.tsx b/public/app/features/logs/components/panel/LogList.tsx index 1934fd420ac..6350723ff89 100644 --- a/public/app/features/logs/components/panel/LogList.tsx +++ b/public/app/features/logs/components/panel/LogList.tsx @@ -59,6 +59,7 @@ export interface Props { logs: LogRowModel[]; logsMeta?: LogsMetaItem[]; logSupportsContext?: (row: LogRowModel) => boolean; + noInteractions?: boolean; onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterString?: (value: string, refId?: string) => void; @@ -124,6 +125,7 @@ export const LogList = ({ logs, logsMeta, logSupportsContext, + noInteractions, onClickFilterLabel, onClickFilterOutLabel, onClickFilterString, @@ -165,6 +167,7 @@ export const LogList = ({ logLineMenuCustomItems={logLineMenuCustomItems} logOptionsStorageKey={logOptionsStorageKey} logSupportsContext={logSupportsContext} + noInteractions={noInteractions} onClickFilterLabel={onClickFilterLabel} onClickFilterOutLabel={onClickFilterOutLabel} onClickFilterString={onClickFilterString} @@ -472,7 +475,7 @@ const LogListComponent = ({ outerRef={scrollRef} overscanCount={5} ref={listRef} - style={{ overflowY: 'scroll' }} + style={wrapLogMessage ? { overflowY: 'scroll' } : { overflow: 'scroll' }} width="100%" > {Renderer} diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx index 8a2f4c50d46..f2c7455fddb 100644 --- a/public/app/features/logs/components/panel/LogListContext.tsx +++ b/public/app/features/logs/components/panel/LogListContext.tsx @@ -11,6 +11,7 @@ import { useState, } from 'react'; +import { createContext as createAssistantContext, ItemDataType, useAssistant } from '@grafana/assistant'; import { CoreApp, DataFrame, @@ -22,10 +23,11 @@ import { shallowCompare, store, } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { t } from '@grafana/i18n'; +import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime'; import { PopoverContent } from '@grafana/ui'; -import { DownloadFormat, checkLogsError, checkLogsSampled, downloadLogs as download } from '../../utils'; +import { checkLogsError, checkLogsSampled, downloadLogs as download, DownloadFormat } from '../../utils'; import { getDisplayedFieldsForLogs } from '../otel/formats'; import { LogLineDetailsMode } from './LogLineDetails'; @@ -63,6 +65,8 @@ export interface LogListContextData extends Omit void; showDetails: LogListModel[]; toggleDetails: (log: LogListModel) => void; + isAssistantAvailable: boolean; + openAssistantByLog: ((log: LogListModel) => void) | undefined; } export const LogListContext = createContext({ @@ -79,6 +83,7 @@ export const LogListContext = createContext({ forceEscape: false, fontSize: 'default', hasUnescapedContent: false, + noInteractions: false, setDedupStrategy: () => {}, setDetailsMode: () => {}, setDetailsWidth: () => {}, @@ -99,6 +104,8 @@ export const LogListContext = createContext({ syntaxHighlighting: true, toggleDetails: () => {}, wrapLogMessage: false, + isAssistantAvailable: false, + openAssistantByLog: () => {}, }); export const useLogListContextData = (key: keyof LogListContextData) => { @@ -153,6 +160,7 @@ export interface Props { logsMeta?: LogsMetaItem[]; logOptionsStorageKey?: string; logSupportsContext?: (row: LogRowModel) => boolean; + noInteractions?: boolean; onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterString?: (value: string, refId?: string) => void; @@ -195,6 +203,7 @@ export const LogListContextProvider = ({ logsMeta, logOptionsStorageKey, logSupportsContext, + noInteractions, onClickFilterLabel, onClickFilterOutLabel, onClickFilterString, @@ -236,6 +245,66 @@ export const LogListContextProvider = ({ const [showDetails, setShowDetails] = useState([]); const [detailsWidth, setDetailsWidthState] = useState(getDetailsWidth(containerElement, logOptionsStorageKey)); const [detailsMode, setDetailsMode] = useState(detailsModeProp ?? 'sidebar'); + const [isAssistantAvailable, openAssistant] = useAssistant(); + + const openAssistantByLog = useCallback( + async (log: LogListModel) => { + if (!openAssistant) { + return; + } + + const datasource = await getDataSourceSrv().get(log.datasourceUid); + const context = []; + if (datasource) { + context.push( + createAssistantContext(ItemDataType.Datasource, { + datasourceUid: datasource.uid, + datasourceName: datasource.name, + datasourceType: datasource.type, + }) + ); + } + openAssistant({ + prompt: `${t('logs.log-line-menu.log-line-explainer', 'Explain this log line in a concise way')}: + + \`\`\` +${log.entry.replaceAll('`', '\\`')} + \`\`\` + `, + context: [ + ...context, + createAssistantContext(ItemDataType.Structured, { + title: t('logs.log-line-menu.log-line', 'Log line'), + data: { + labels: log.labels, + value: log.entry, + timestamp: log.timestamp, + }, + }), + ], + }); + }, + [openAssistant] + ); + + useEffect(() => { + if (noInteractions) { + return; + } + reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, { + dedupStrategy, + fontSize, + forceEscape: logListState.forceEscape, + showTime, + syntaxHighlighting, + wrapLogMessage, + detailsWidth, + detailsMode, + withDisplayedFields: displayedFields.length > 0, + }); + // Just once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { if (displayedFields.length > 0 || !config.featureToggles.otelLogsFormatting || !setDisplayedFields) { @@ -444,9 +513,7 @@ export const LogListContextProvider = ({ ); const closeDetails = useCallback(() => { - if (showDetails.length) { - removeDetailsScrollPosition(showDetails[0]); - } + showDetails.forEach((log) => removeDetailsScrollPosition(log)); setShowDetails([]); }, [showDetails]); @@ -455,12 +522,13 @@ export const LogListContextProvider = ({ if (!enableLogDetails) { return; } - const found = showDetails.findIndex((stateLog) => stateLog === log || stateLog.uid === log.uid); - if (found >= 0) { + const found = showDetails.find((stateLog) => stateLog === log || stateLog.uid === log.uid); + if (found) { + removeDetailsScrollPosition(found); setShowDetails(showDetails.filter((stateLog) => stateLog !== log && stateLog.uid !== log.uid)); } else { // Supporting one displayed details for now - setShowDetails([log]); + setShowDetails([...showDetails, log]); } }, [enableLogDetails, showDetails] @@ -510,6 +578,7 @@ export const LogListContextProvider = ({ logSupportsContext, logLineMenuCustomItems, logOptionsStorageKey, + noInteractions: noInteractions ?? false, onClickFilterLabel, onClickFilterOutLabel, onClickFilterString, @@ -547,6 +616,8 @@ export const LogListContextProvider = ({ syntaxHighlighting: logListState.syntaxHighlighting, toggleDetails, wrapLogMessage: logListState.wrapLogMessage, + isAssistantAvailable, + openAssistantByLog, }} > {children} @@ -605,3 +676,12 @@ export function getDetailsScrollPosition(log: LogListModel) { export function removeDetailsScrollPosition(log: LogListModel) { detailsScrollMap.delete(log.uid); } + +const reportInteractionOnce = (interactionName: string, properties?: Record) => { + const key = `logs.log-list-context.events.${interactionName}`; + if (sessionStorage.getItem(key)) { + return; + } + sessionStorage.setItem(key, '1'); + reportInteraction(interactionName, properties); +}; diff --git a/public/app/features/logs/components/panel/LogListControls.test.tsx b/public/app/features/logs/components/panel/LogListControls.test.tsx index db8e59134b0..2acdf38abe0 100644 --- a/public/app/features/logs/components/panel/LogListControls.test.tsx +++ b/public/app/features/logs/components/panel/LogListControls.test.tsx @@ -17,6 +17,13 @@ jest.mock('../../utils', () => ({ downloadLogs: jest.fn(), })); +jest.mock('@grafana/assistant', () => { + return { + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn().mockReturnValue([true, jest.fn()]), + }; +}); + const fontSize: LogListFontSize = 'default'; const contextProps = { app: CoreApp.Unknown, @@ -31,6 +38,8 @@ const contextProps = { sortOrder: LogsSortOrder.Ascending, syntaxHighlighting: false, wrapLogMessage: false, + isAssistantAvailable: false, + openAssistantByLog: () => {}, }; describe('LogListControls', () => { diff --git a/public/app/features/logs/components/panel/LogListSearch.tsx b/public/app/features/logs/components/panel/LogListSearch.tsx index c47dea1449f..3cdd1cbbe3d 100644 --- a/public/app/features/logs/components/panel/LogListSearch.tsx +++ b/public/app/features/logs/components/panel/LogListSearch.tsx @@ -4,6 +4,7 @@ import { VariableSizeList } from 'react-window'; import { escapeRegex, GrafanaTheme2, shallowCompare } from '@grafana/data'; import { t } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; import { IconButton, Input, useStyles2 } from '@grafana/ui'; import { useLogListContext } from './LogListContext'; @@ -26,10 +27,11 @@ export const LogListSearch = ({ listRef, logs }: Props) => { searchVisible, toggleFilterLogs, } = useLogListSearchContext(); - const { displayedFields } = useLogListContext(); + const { displayedFields, noInteractions } = useLogListContext(); const [search, setSearch] = useState(''); const [currentResult, setCurrentResult] = useState(null); const inputRef = useRef(''); + const searchUsedRef = useRef(false); const styles = useStyles2(getStyles); const matches = useMemo(() => { @@ -39,12 +41,19 @@ export const LogListSearch = ({ listRef, logs }: Props) => { return findMatchingLogs(logs, search, displayedFields); }, [displayedFields, logs, search, searchVisible]); - const handleChange = useCallback((e: ChangeEvent) => { - inputRef.current = e.target.value; - startTransition(() => { - setSearch(inputRef.current); - }); - }, []); + const handleChange = useCallback( + (e: ChangeEvent) => { + inputRef.current = e.target.value; + startTransition(() => { + setSearch(inputRef.current); + }); + if (!searchUsedRef.current && !noInteractions) { + reportInteraction('logs_log_list_search_used'); + searchUsedRef.current = true; + } + }, + [noInteractions] + ); const prevResult = useCallback(() => { if (currentResult === null) { diff --git a/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx b/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx index 131afaa32cb..53b020bbe88 100644 --- a/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx +++ b/public/app/features/logs/components/panel/__mocks__/LogListContext.tsx @@ -7,6 +7,13 @@ import { LogLineDetailsMode } from '../LogLineDetails'; import { LogListContextData, Props } from '../LogListContext'; import { LogListModel } from '../processing'; +jest.mock('@grafana/assistant', () => { + return { + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn().mockReturnValue([true, jest.fn()]), + }; +}); + export const LogListContext = createContext({ app: CoreApp.Unknown, closeDetails: () => {}, @@ -43,6 +50,8 @@ export const LogListContext = createContext({ setDetailsMode: function (mode: LogLineDetailsMode): void { throw new Error('Function not implemented.'); }, + isAssistantAvailable: false, + openAssistantByLog: () => {}, }); export const useLogListContextData = (key: keyof LogListContextData) => { @@ -97,6 +106,8 @@ export const defaultValue: LogListContextData = { showTime: false, sortOrder: LogsSortOrder.Ascending, wrapLogMessage: false, + isAssistantAvailable: false, + openAssistantByLog: () => {}, }; export const defaultProps: Props = { diff --git a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx index 0891d24b7a5..e39105740b8 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx @@ -52,7 +52,7 @@ export const SnapshotListTable = () => { You can create a snapshot of any dashboard through the Share modal.{' '} Learn more diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index 9a83a03b688..0cfbf334fb6 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -87,7 +87,7 @@ export function PluginDetailsBody({ plugin, queryParams, pageId, info, showDetai return ; } - if (pageId === PluginTabIds.PLUGINDETAILS && config.featureToggles.pluginsDetailsRightPanel && showDetails) { + if (pageId === PluginTabIds.PLUGINDETAILS && showDetails) { return (
diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx index 4a5a7b5c7bd..da4eef2f0d4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx @@ -106,14 +106,7 @@ describe('PluginDetailsPage', () => { expect(screen.getByText('Plugin not found')).toBeInTheDocument(); }); - it('should not show right panel when feature toggle is disabled', () => { - config.featureToggles.pluginsDetailsRightPanel = false; - render(); - expect(screen.queryByTestId('plugin-details-panel')).not.toBeInTheDocument(); - }); - - it('should show right panel when feature toggle is enabled and screen is wide', () => { - config.featureToggles.pluginsDetailsRightPanel = true; + it('should show right panel when screen is wide', () => { window.matchMedia = jest.fn().mockImplementation((query) => ({ matches: query !== '(max-width: 600px)', media: query, @@ -128,7 +121,6 @@ describe('PluginDetailsPage', () => { }); it('should show "Plugin details" tab when screen is narrow', () => { - config.featureToggles.pluginsDetailsRightPanel = true; window.matchMedia = jest.fn().mockImplementation((query) => ({ matches: query === '(max-width: 600px)', media: query, @@ -158,7 +150,6 @@ describe('PluginDetailsPage', () => { }); it('should not show last version in plugin details panel when plugin is core', () => { - config.featureToggles.pluginsDetailsRightPanel = true; window.matchMedia = jest.fn().mockImplementation((query) => ({ matches: query !== '(max-width: 600px)', media: query, diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index d5a69de88ec..0e651a8e4bf 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -5,7 +5,6 @@ import { useMedia } from 'react-use'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { config } from '@grafana/runtime'; import { Alert, Box, Stack, TabContent, TextLink, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { AppNotificationSeverity } from 'app/types/appNotifications'; @@ -83,10 +82,8 @@ export function PluginDetailsPage({ ); } - const conditionalProps = !config.featureToggles.pluginsDetailsRightPanel ? { info: info } : {}; - return ( - + @@ -102,9 +99,7 @@ export function PluginDetailsPage({ /> - {!isNarrowScreen && config.featureToggles.pluginsDetailsRightPanel && ( - - )} + {!isNarrowScreen && } ); diff --git a/public/app/features/plugins/admin/components/PluginSubtitle.tsx b/public/app/features/plugins/admin/components/PluginSubtitle.tsx index 0a66d863938..6e725eb014f 100644 --- a/public/app/features/plugins/admin/components/PluginSubtitle.tsx +++ b/public/app/features/plugins/admin/components/PluginSubtitle.tsx @@ -2,7 +2,6 @@ import { css } from '@emotion/css'; import { Fragment } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { Alert, Stack, useStyles2 } from '@grafana/ui'; import { InstallControlsWarning } from '../components/InstallControls/InstallControlsWarning'; @@ -46,7 +45,7 @@ export const PluginSubtitle = ({ plugin }: Props) => {
{plugin?.description &&
{plugin?.description}
} - {!config.featureToggles.pluginsDetailsRightPanel && !!plugin?.details?.links?.length && ( + {!!plugin?.details?.links?.length && ( {plugin.details.links.map((link, index) => ( diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index f0eebd3bd0a..6f0f076d54b 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -74,7 +74,7 @@ export const usePluginDetailsTabs = ( }); } - if (isPublished && isNarrowScreen && config.featureToggles.pluginsDetailsRightPanel) { + if (isPublished && isNarrowScreen) { navModelChildren.push({ text: PluginTabLabels.PLUGINDETAILS, id: PluginTabIds.PLUGINDETAILS, diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index b725d3a9d58..b7aaed5cef0 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -885,11 +885,6 @@ describe('Plugin details page', () => { isDataSourceEditor: false, isOrgAdmin: true, }); - config.featureToggles.pluginsDetailsRightPanel = true; - }); - - afterAll(() => { - config.featureToggles.pluginsDetailsRightPanel = false; }); it('should display Latest release date and report a concern information', async () => { diff --git a/public/app/features/plugins/loader/cache.ts b/public/app/features/plugins/loader/cache.ts index 00e77489efd..877d7fc485b 100644 --- a/public/app/features/plugins/loader/cache.ts +++ b/public/app/features/plugins/loader/cache.ts @@ -54,7 +54,20 @@ export function getPluginFromCache(path: string): CachedPlugin | undefined { export function extractCacheKeyFromPath(path: string) { const regex = /\/?public\/plugins\/([^\/]+)\//; const match = path.match(regex); - return match ? match[1] : null; + + if (match) { + return match[1]; + } + + // Decoupled core plugins can be loaded by alternative paths + const decoupledPluginRegex = /\/?public\/app\/plugins\/(?:datasource|panel)\/([^\/]+)\//; + const decoupledPluginMatch = path.match(decoupledPluginRegex); + + if (decoupledPluginMatch) { + return decoupledPluginMatch[1]; + } + + return null; } function getCacheKey(address: string): string | undefined { diff --git a/public/app/features/profile/UserProfileEditTabs.tsx b/public/app/features/profile/UserProfileEditTabs.tsx index 1faaba251a3..c0fe9db3e4d 100644 --- a/public/app/features/profile/UserProfileEditTabs.tsx +++ b/public/app/features/profile/UserProfileEditTabs.tsx @@ -43,7 +43,9 @@ export function UserProfileEditTabs(props: Props): ReactElement { {Boolean(activeTab) && ( - {activeTab?.components.map((Component, index) => )} + {activeTab?.components.map((Component, index) => ( + + ))} )} diff --git a/public/app/features/provisioning/Config/ConfigForm.tsx b/public/app/features/provisioning/Config/ConfigForm.tsx index f69fa3bd4ec..177f788acec 100644 --- a/public/app/features/provisioning/Config/ConfigForm.tsx +++ b/public/app/features/provisioning/Config/ConfigForm.tsx @@ -2,11 +2,10 @@ import { useEffect, useMemo, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom-v5-compat'; -import { Trans, t } from '@grafana/i18n'; +import { t } from '@grafana/i18n'; import { Button, Checkbox, - Combobox, ControlledCollapse, Field, Input, @@ -19,26 +18,21 @@ import { Repository } from 'app/api/clients/provisioning/v0alpha1'; import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt'; import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo'; +import { getGitProviderFields, getLocalProviderFields } from '../Wizard/fields'; import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository'; import { RepositoryFormData } from '../types'; import { dataToSpec } from '../utils/data'; +import { getRepositoryTypeConfig, isGitProvider } from '../utils/repositoryTypes'; import { ConfigFormGithubCollapse } from './ConfigFormGithubCollapse'; import { getDefaultValues } from './defaults'; // This needs to be a function for translations to work -const getOptions = () => { - const typeOptions = [ - { value: 'github', label: t('provisioning.config-form.option-github', 'GitHub') }, - { value: 'local', label: t('provisioning.config-form.option-local', 'Local') }, - ]; - - const targetOptions = [ +const getTargetOptions = () => { + return [ { value: 'instance', label: t('provisioning.config-form.option-entire-instance', 'Entire instance') }, { value: 'folder', label: t('provisioning.config-form.option-managed-folder', 'Managed folder') }, ]; - - return [typeOptions, targetOptions]; }; export interface ConfigFormProps { @@ -59,10 +53,15 @@ export function ConfigForm({ data }: ConfigFormProps) { const isEdit = Boolean(data?.metadata?.name); const [tokenConfigured, setTokenConfigured] = useState(isEdit); + const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const [type, readOnly] = watch(['type', 'readOnly']); - const [typeOptions, targetOptions] = useMemo(() => getOptions(), []); - const [isLoading, setIsLoading] = useState(false); + const targetOptions = useMemo(() => getTargetOptions(), []); + const isGitBased = isGitProvider(type); + + // Get field configurations based on provider type + const gitFields = isGitBased ? getGitProviderFields(type) : null; + const localFields = type === 'local' ? getLocalProviderFields(type) : null; useEffect(() => { if (request.isSuccess) { @@ -76,217 +75,214 @@ export function ConfigForm({ data }: ConfigFormProps) { const onSubmit = async (form: RepositoryFormData) => { setIsLoading(true); - const spec = dataToSpec(form); - if (spec.github) { - spec.github.token = form.token || data?.spec?.github?.token; - // If we're still keeping this as GitHub, persist the old token. If we set a new one, it'll be re-encrypted into here. - spec.github.encryptedToken = data?.spec?.github?.encryptedToken; + try { + const spec = dataToSpec(form, data); + await submitData(spec); + } finally { + setIsLoading(false); } - await submitData(spec); - setIsLoading(false); }; - // NOTE: We do not want the lint option to be listed. return (
- - { - return ( - onChange(value?.value)} - placeholder={t('provisioning.config-form.placeholder-select-repository-type', 'Select repository type')} - disabled={!!data?.spec} - {...field} - /> - ); - }} - /> - - - - - {type === 'github' && ( - <> - - { - return ( - { - setValue('token', ''); - setTokenConfigured(false); - }} - /> - ); - }} - /> - - - - - - - - - - - - - )} - - {type === 'local' && ( + + + + - )} - - - { - if (e.target.checked) { - setValue('prWorkflow', false); - } - }, - })} - label={t('provisioning.finish-step.label-read-only', 'Read only')} - description={t( - 'provisioning.config-form.description-read-only', - "Resources can't be modified through Grafana." - )} - /> - - - - - Allows users to choose whether to open a pull request when saving changes. If the repository does not - allow direct changes to the main branch, a pull request may still be required. - - } - /> - - {type === 'github' && } - - - - - - - { - return ( - + + { + return ( + { + setValue('token', ''); + setTokenConfigured(false); + }} + /> + ); + }} + /> + + {gitFields.tokenUserConfig && ( + + - ); - }} - /> - - - - - + + )} + {type === 'github' && } + + + + + + + + + + + )} - - + {localFields && ( + + + + )} + + + { + if (e.target.checked) { + setValue('prWorkflow', false); + } + }, + })} + label={t('provisioning.finish-step.label-read-only', 'Read only')} + description={t( + 'provisioning.config-form.description-read-only', + "Resources can't be modified through Grafana." + )} + /> + + + {gitFields && ( + + + + )} + {type === 'github' && } + + {isGitBased && ( + + + + + + + { + return ( + + ); + }} + /> + + + + + + + )} + + + + ); diff --git a/public/app/features/provisioning/GettingStarted/FeaturesList.tsx b/public/app/features/provisioning/GettingStarted/FeaturesList.tsx index 165ee52cbd1..e3352e461b5 100644 --- a/public/app/features/provisioning/GettingStarted/FeaturesList.tsx +++ b/public/app/features/provisioning/GettingStarted/FeaturesList.tsx @@ -2,31 +2,29 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; -import { Stack, Text, Box, LinkButton, useStyles2 } from '@grafana/ui'; -import { Repository } from 'app/api/clients/provisioning/v0alpha1'; +import { Box, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui'; -import { ConnectRepositoryButton } from '../Shared/ConnectRepositoryButton'; +import { RepositoryTypeCards } from '../Shared/RepositoryTypeCards'; interface FeaturesListProps { - repos?: Repository[]; hasRequiredFeatures: boolean; onSetupFeatures: () => void; } -export const FeaturesList = ({ repos, hasRequiredFeatures, onSetupFeatures }: FeaturesListProps) => { +export const FeaturesList = ({ hasRequiredFeatures, onSetupFeatures }: FeaturesListProps) => { const styles = useStyles2(getStyles); return ( - Get started with GitSync + Get started with Git Sync
  • - Manage dashboards as code in GitHub and provision updates automatically + Manage dashboards as code in Git and provision updates automatically
  • @@ -45,7 +43,7 @@ export const FeaturesList = ({ repos, hasRequiredFeatures, onSetupFeatures }: Fe ) : ( - + )} diff --git a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx index dd68b0f304f..de986fe4748 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx @@ -6,8 +6,9 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { Alert, Stack, useStyles2 } from '@grafana/ui'; -import { useGetFrontendSettingsQuery, Repository } from 'app/api/clients/provisioning/v0alpha1'; -import provisioningSvg from 'img/provisioning/provisioning.svg'; +import { Repository, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1'; + +import provisioningSvg from '../img/provisioning.svg'; import { EnhancedFeatures } from './EnhancedFeatures'; import { FeaturesList } from './FeaturesList'; @@ -151,17 +152,16 @@ export default function GettingStarted({ items }: Props) { )} +
    + {'Grafana +
    { setSetupType('required-features'); setShowModal(true); }} /> -
    - {'Grafana -
    {(!hasPublicAccess || !hasImageRenderer) && hasItems && ( { @@ -47,14 +67,17 @@ export function FinishedJobStatus({ jobUid, repositoryName }: FinishedJobProps) }, [finishedQuery, job, setStepStatusInfo]); if (retryFailed) { - setStepStatusInfo({ status: 'error' }); - return ( - - - The job may have been deleted or could not be retrieved. Cancel the current process and start again. - - - ); + setStepStatusInfo({ + status: 'error', + error: { + title: t('provisioning.job-status.no-job-found', 'No job found'), + message: t( + 'provisioning.job-status.no-job-found-message', + 'The job may have been deleted or could not be retrieved. Cancel the current process and start again.' + ), + }, + }); + return null; } if (!job || finishedQuery.isLoading || finishedQuery.isFetching) { diff --git a/public/app/features/provisioning/Job/JobContent.tsx b/public/app/features/provisioning/Job/JobContent.tsx index 864da5adbc1..867f7844fda 100644 --- a/public/app/features/provisioning/Job/JobContent.tsx +++ b/public/app/features/provisioning/Job/JobContent.tsx @@ -1,9 +1,12 @@ +import { useEffect, useRef } from 'react'; + import { Trans, t } from '@grafana/i18n'; -import { Alert, ControlledCollapse, Spinner, Stack, Text } from '@grafana/ui'; +import { ControlledCollapse, Spinner, Stack, Text } from '@grafana/ui'; import { Job } from 'app/api/clients/provisioning/v0alpha1'; import { RepositoryLink } from '../Repository/RepositoryLink'; import ProgressBar from '../Shared/ProgressBar'; +import { useStepStatus } from '../Wizard/StepStatusContext'; import { JobSummary } from './JobSummary'; @@ -13,6 +16,9 @@ export interface JobContentProps { } export function JobContent({ job, isFinishedJob = false }: JobContentProps) { + const { setStepStatusInfo } = useStepStatus(); + const errorSetRef = useRef(false); + if (!job?.status) { return null; } @@ -20,39 +26,60 @@ export function JobContent({ job, isFinishedJob = false }: JobContentProps) { const { state, message, progress, summary, errors } = job.status; const repoName = job.metadata?.labels?.['provisioning.grafana.app/repository']; - const getStatusDisplay = () => { + // Update step status based on job state + useEffect(() => { + if (!state) { + return; + } + switch (state) { case 'success': - return ( - - ); + setStepStatusInfo({ status: 'success' }); + break; + case 'warning': + if (!errorSetRef.current) { + setStepStatusInfo({ + status: 'warning', + warning: { + title: t('provisioning.job-status.status.title-warning-running-job', 'Job completed with warnings'), + message: errors?.length ? errors : message, + }, + }); + errorSetRef.current = true; + } + break; case 'error': - return ( - - {message ?? errors?.join('\n')} - - ); + if (!errorSetRef.current) { + setStepStatusInfo({ + status: 'error', + error: { + title: t('provisioning.job-status.status.title-error-running-job', 'Error running job'), + message: errors?.length ? errors : message, + }, + }); + errorSetRef.current = true; + } + break; + case 'working': + case 'pending': + setStepStatusInfo({ status: 'running' }); + break; + default: + break; } - return ( - - {['working', 'pending'].includes(state ?? '') && } - - {message ?? state ?? ''} - - - ); - }; + }, [state, message, errors, setStepStatusInfo]); return ( - {getStatusDisplay()} + {['working', 'pending'].includes(state ?? '') && ( + + + + {message ?? state ?? t('provisioning.job-status.starting', 'Starting...')} + + + )} {state && !['success', 'error'].includes(state) && ( diff --git a/public/app/features/provisioning/Job/JobStatus.tsx b/public/app/features/provisioning/Job/JobStatus.tsx index f003245e263..ee05d56c051 100644 --- a/public/app/features/provisioning/Job/JobStatus.tsx +++ b/public/app/features/provisioning/Job/JobStatus.tsx @@ -1,4 +1,4 @@ -import { Trans } from '@grafana/i18n'; +import { Trans, t } from '@grafana/i18n'; import { Spinner, Stack, Text } from '@grafana/ui'; import { Job, useListJobQuery } from 'app/api/clients/provisioning/v0alpha1'; @@ -36,7 +36,12 @@ export function JobStatus({ watch }: JobStatusProps) { } if (activeQuery.isError) { - setStepStatusInfo({ status: 'error', error: 'Error fetching active job' }); + setStepStatusInfo({ + status: 'error', + error: { + title: t('provisioning.job-status.title.error-fetching-active-job', 'Error fetching active job'), + }, + }); return null; } diff --git a/public/app/features/provisioning/Job/RecentJobs.tsx b/public/app/features/provisioning/Job/RecentJobs.tsx index 8cf520979d3..550511b5069 100644 --- a/public/app/features/provisioning/Job/RecentJobs.tsx +++ b/public/app/features/provisioning/Job/RecentJobs.tsx @@ -2,10 +2,11 @@ import { useMemo } from 'react'; import { intervalToAbbreviatedDurationString, TraceKeyValuePair } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Badge, Box, Card, Icon, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui'; +import { Alert, Badge, Box, Card, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui'; import { Job, Repository, SyncStatus } from 'app/api/clients/provisioning/v0alpha1'; import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable'; +import { ProvisioningAlert } from '../Shared/ProvisioningAlert'; import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs'; import { formatTimestamp } from '../utils/time'; @@ -26,8 +27,11 @@ const getStatusColor = (state?: SyncStatus['state']) => { case 'success': return 'green'; case 'working': - case 'pending': + return 'blue'; + case 'warning': return 'orange'; + case 'pending': + return 'darkgrey'; case 'error': return 'red'; default: @@ -123,21 +127,7 @@ function ExpandedRow({ row }: ExpandedRowProps) { )} - {hasErrors && ( - - {row.status?.errors?.map( - (error, index) => - error.trim() && ( - - - - {error} - - - ) - )} - - )} + {hasErrors && } {hasSummary && ( @@ -209,7 +199,7 @@ export function RecentJobs({ repo }: Props) { } return ( - + Jobs diff --git a/public/app/features/provisioning/Repository/RepositoryActions.tsx b/public/app/features/provisioning/Repository/RepositoryActions.tsx index 6c1adc21f1d..d1b710ee2b2 100644 --- a/public/app/features/provisioning/Repository/RepositoryActions.tsx +++ b/public/app/features/provisioning/Repository/RepositoryActions.tsx @@ -4,7 +4,8 @@ import { Repository } from 'app/api/clients/provisioning/v0alpha1'; import { StatusBadge } from '../Shared/StatusBadge'; import { PROVISIONING_URL } from '../constants'; -import { getRepoHref } from '../utils/git'; +import { getRepoHrefForProvider } from '../utils/git'; +import { getRepositoryTypeConfig } from '../utils/repositoryTypes'; import { DeleteRepositoryButton } from './DeleteRepositoryButton'; import { SyncRepository } from './SyncRepository'; @@ -15,14 +16,18 @@ interface RepositoryActionsProps { export function RepositoryActions({ repository }: RepositoryActionsProps) { const name = repository.metadata?.name ?? ''; - const repoHref = getRepoHref(repository.spec?.github); + const repoHref = getRepoHrefForProvider(repository.spec); + + const repoType = repository.spec?.type; + const repoConfig = repoType ? getRepositoryTypeConfig(repoType) : undefined; + const providerIcon = repoConfig?.icon || 'external-link-alt'; return ( {repoHref && ( - )} diff --git a/public/app/features/provisioning/Repository/RepositoryCard.tsx b/public/app/features/provisioning/Repository/RepositoryCard.tsx index e2a022cebb5..e56f8655eb9 100644 --- a/public/app/features/provisioning/Repository/RepositoryCard.tsx +++ b/public/app/features/provisioning/Repository/RepositoryCard.tsx @@ -58,7 +58,7 @@ export function RepositoryCard({ repository }: Props) { }; return ( - + diff --git a/public/app/features/provisioning/Repository/RepositoryOverview.tsx b/public/app/features/provisioning/Repository/RepositoryOverview.tsx index ba18bf32fcf..b4f7bfd019a 100644 --- a/public/app/features/provisioning/Repository/RepositoryOverview.tsx +++ b/public/app/features/provisioning/Repository/RepositoryOverview.tsx @@ -6,6 +6,7 @@ import { Box, Card, CellProps, Grid, InteractiveTable, LinkButton, Stack, Text, import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1'; import { RecentJobs } from '../Job/RecentJobs'; +import { MessageList } from '../Shared/MessageList'; import { formatTimestamp } from '../utils/time'; import { CheckRepository } from './CheckRepository'; @@ -51,7 +52,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
    - + Resources @@ -73,7 +74,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
    {repo.status?.health && (
    - + Health @@ -129,7 +130,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
    )}
    - + Pull status @@ -192,13 +193,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
    - - {status.sync.message.map((msg, idx) => ( - - {msg} - - ))} - +
    )} @@ -211,7 +206,7 @@ export function RepositoryOverview({ repo }: { repo: Repository }) {
{repo.status?.webhook && (
- + Webhook diff --git a/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx b/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx index 385d2ad2ed6..64ace020dcb 100644 --- a/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx +++ b/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx @@ -1,26 +1,22 @@ import { useNavigate } from 'react-router-dom-v5-compat'; -import { Trans, t } from '@grafana/i18n'; -import { Alert, Button, Dropdown, Icon, LinkButton, Menu, Stack } from '@grafana/ui'; +import { Trans } from '@grafana/i18n'; +import { Alert, Button, Dropdown, Icon, Menu, Stack } from '@grafana/ui'; import { Repository } from 'app/api/clients/provisioning/v0alpha1'; +import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1/endpoints.gen'; -import { RepoType } from '../Wizard/types'; -import { CONNECT_URL } from '../constants'; +import { CONNECT_URL, DEFAULT_REPOSITORY_TYPES } from '../constants'; import { checkSyncSettings } from '../utils/checkSyncSettings'; +import { getOrderedRepositoryConfigs } from '../utils/repositoryTypes'; interface Props { items?: Repository[]; - showDropdown?: boolean; } -type ConnectUrl = `${typeof CONNECT_URL}/${RepoType}`; - -const gitURL: ConnectUrl = `${CONNECT_URL}/github`; -const localURL: ConnectUrl = `${CONNECT_URL}/local`; - -export function ConnectRepositoryButton({ items, showDropdown = false }: Props) { +export function ConnectRepositoryButton({ items }: Props) { const state = checkSyncSettings(items); const navigate = useNavigate(); + const { data: frontendSettings } = useGetFrontendSettingsQuery(); if (state.instanceConnected) { return null; @@ -32,52 +28,39 @@ export function ConnectRepositoryButton({ items, showDropdown = false }: Props) + > + Repository limit reached ({'{{count}}'}) + ); } - if (showDropdown) { - return ( - - { - navigate(gitURL); - }} - /> - { - navigate(localURL); - }} - /> - - } - > - - - ); - } + const availableTypes = frontendSettings?.availableRepositoryTypes || DEFAULT_REPOSITORY_TYPES; + const { orderedConfigs } = getOrderedRepositoryConfigs(availableTypes); return ( - - - Configure Git Sync - - - Configure file provisioning - - + + {orderedConfigs.map((config) => { + return ( + navigate(`${CONNECT_URL}/${config.type}`)} + /> + ); + })} + + } + > + + ); } diff --git a/public/app/features/provisioning/Shared/MessageList.tsx b/public/app/features/provisioning/Shared/MessageList.tsx new file mode 100644 index 00000000000..2d7f0f15f2f --- /dev/null +++ b/public/app/features/provisioning/Shared/MessageList.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Text, useStyles2 } from '@grafana/ui'; + +interface MessageListProps { + messages: string[]; + variant?: 'body' | 'bodySmall'; +} + +export function MessageList({ messages, variant }: MessageListProps) { + const styles = useStyles2(getStyles); + + return ( +
    + {messages.map((msg, index) => ( +
  • {variant ? {msg} : msg}
  • + ))} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + messageList: css({ + margin: 0, + paddingLeft: theme.spacing(3), + }), +}); diff --git a/public/app/features/provisioning/Shared/ProgressBar.tsx b/public/app/features/provisioning/Shared/ProgressBar.tsx index 681321bb0b1..a9d9e3c57b0 100644 --- a/public/app/features/provisioning/Shared/ProgressBar.tsx +++ b/public/app/features/provisioning/Shared/ProgressBar.tsx @@ -5,9 +5,10 @@ import { useStyles2 } from '@grafana/ui'; interface ProgressBarProps { progress?: number; + topBottomSpacing?: number; } -const ProgressBar = ({ progress }: ProgressBarProps) => { - const styles = useStyles2(getStyles); +const ProgressBar = ({ progress, topBottomSpacing }: ProgressBarProps) => { + const styles = useStyles2(getStyles, topBottomSpacing); if (progress === undefined) { return null; @@ -20,14 +21,14 @@ const ProgressBar = ({ progress }: ProgressBarProps) => { ); }; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (theme: GrafanaTheme2, topBottomSpacing = 2) => ({ container: css({ height: '10px', width: '400px', backgroundColor: theme.colors.background.secondary, borderRadius: theme.shape.radius.pill, overflow: 'hidden', - margin: theme.spacing(2, 0), + margin: theme.spacing(topBottomSpacing, 0), }), filler: css({ height: '100%', diff --git a/public/app/features/provisioning/Shared/ProvisioningAlert.tsx b/public/app/features/provisioning/Shared/ProvisioningAlert.tsx new file mode 100644 index 00000000000..94fb45423cc --- /dev/null +++ b/public/app/features/provisioning/Shared/ProvisioningAlert.tsx @@ -0,0 +1,51 @@ +import { t } from '@grafana/i18n'; +import { Alert } from '@grafana/ui'; + +import { ProvisioningErrorInfo } from '../types'; + +import { MessageList } from './MessageList'; + +interface ProvisioningAlertProps { + error?: string | ProvisioningErrorInfo; + warning?: string | ProvisioningErrorInfo; +} + +const getTitle = (alert: string | ProvisioningErrorInfo, isWarning = false) => { + if (typeof alert === 'string') { + return alert; + } + + if (isWarning) { + return alert.title || t('provisioning.warning-title-default', 'Warning'); + } else { + return alert.title || t('provisioning.error-title-default', 'Error'); + } +}; + +const getMessage = (alert: string | ProvisioningErrorInfo) => { + if (typeof alert === 'string' || !alert.message) { + return null; + } + + if (Array.isArray(alert.message)) { + return ; + } + + return alert.message; +}; + +export function ProvisioningAlert({ error, warning }: ProvisioningAlertProps) { + const alertData = error || warning; + const isWarning = Boolean(warning); + const severity = isWarning ? 'warning' : 'error'; + + if (!alertData) { + return null; + } + + return ( + + {getMessage(alertData)} + + ); +} diff --git a/public/app/features/provisioning/Shared/RepositoryList.tsx b/public/app/features/provisioning/Shared/RepositoryList.tsx index 76560f88f5d..368ac5b644d 100644 --- a/public/app/features/provisioning/Shared/RepositoryList.tsx +++ b/public/app/features/provisioning/Shared/RepositoryList.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; -import { Trans, t } from '@grafana/i18n'; -import { EmptySearchResult, FilterInput, Stack } from '@grafana/ui'; +import { t } from '@grafana/i18n'; +import { EmptyState, FilterInput, Stack } from '@grafana/ui'; import { Repository } from 'app/api/clients/provisioning/v0alpha1'; import { RepositoryCard } from '../Repository/RepositoryCard'; @@ -27,18 +27,20 @@ export function RepositoryList({ items }: Props) { value={query} onChange={setQuery} /> - + )} - + {filteredItems.length ? ( filteredItems.map((item) => ) ) : ( - - - No results matching your query - - + )} diff --git a/public/app/features/provisioning/Shared/RepositoryTypeCards.tsx b/public/app/features/provisioning/Shared/RepositoryTypeCards.tsx new file mode 100644 index 00000000000..7eab2d894a3 --- /dev/null +++ b/public/app/features/provisioning/Shared/RepositoryTypeCards.tsx @@ -0,0 +1,92 @@ +import { css } from '@emotion/css'; + +import { Trans } from '@grafana/i18n'; +import { Card, Icon, Stack, Text, useStyles2 } from '@grafana/ui'; +import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1/endpoints.gen'; + +import { CONNECT_URL, DEFAULT_REPOSITORY_TYPES } from '../constants'; +import { getOrderedRepositoryConfigs } from '../utils/repositoryTypes'; + +export function RepositoryTypeCards() { + const styles = useStyles2(getStyles); + const { data: frontendSettings } = useGetFrontendSettingsQuery(); + + const availableTypes = frontendSettings?.availableRepositoryTypes || DEFAULT_REPOSITORY_TYPES; + const { gitProviders, otherProviders } = getOrderedRepositoryConfigs(availableTypes); + + return ( + + {gitProviders.length > 0 && ( + + + Choose a provider: + + + + {gitProviders.map((config) => ( + + + {config.logo ? ( + {config.label} + ) : ( + + )} + + Configure with {'{{ provider }}'} + + + + ))} + + + )} + + + + + If your provider is not listed: + + + + + {otherProviders.map((config) => ( + + + {config.logo ? ( + {config.label} + ) : ( + + )} + {config.type === 'local' ? ( + Configure file provisioning + ) : ( + + Configure with {'{{ provider }}'} + + )} + + + ))} + + + + ); +} + +function getStyles() { + return { + card: css({ + width: 220, + }), + logo: css({ + width: 36, + height: 36, + }), + }; +} diff --git a/public/app/features/provisioning/Shared/StatusBadge.tsx b/public/app/features/provisioning/Shared/StatusBadge.tsx index afd010c3b67..7923cf6549c 100644 --- a/public/app/features/provisioning/Shared/StatusBadge.tsx +++ b/public/app/features/provisioning/Shared/StatusBadge.tsx @@ -27,7 +27,7 @@ export function StatusBadge({ repo }: StatusBadgeProps) { text = 'Automatic pulling disabled'; icon = 'info-circle'; } else if (!repo.status?.sync?.state?.length) { - color = 'orange'; + color = 'darkgrey'; text = 'Pending'; icon = 'spinner'; tooltip = 'Waiting for health check to run'; @@ -40,8 +40,13 @@ export function StatusBadge({ repo }: StatusBadgeProps) { color = 'green'; break; case 'working': - case 'pending': + case 'warning': color = 'orange'; + text = 'warning'; + icon = 'exclamation-triangle'; + break; + case 'pending': + color = 'darkgrey'; text = 'Pulling'; icon = 'spinner'; break; diff --git a/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx b/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx index 29ad097ae63..59dd57f2840 100644 --- a/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx +++ b/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; @@ -7,7 +7,7 @@ import { useGetRepositoryFilesQuery, useGetResourceStatsQuery } from 'app/api/cl import { BootstrapStep, Props } from './BootstrapStep'; import { StepStatusProvider } from './StepStatusContext'; -import { getResourceStats, useModeOptions } from './actions'; +import { useModeOptions } from './hooks/useModeOptions'; import { WizardFormData } from './types'; jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ @@ -15,11 +15,14 @@ jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ useGetResourceStatsQuery: jest.fn(), })); -jest.mock('./actions', () => ({ - getResourceStats: jest.fn(), +jest.mock('./hooks/useModeOptions', () => ({ useModeOptions: jest.fn(), })); +jest.mock('./hooks/useResourceStats', () => ({ + useResourceStats: jest.fn(), +})); + // Wrapper component to provide form context function FormWrapper({ children, defaultValues }: { children: ReactNode; defaultValues?: Partial }) { const methods = useForm({ @@ -53,7 +56,6 @@ function setup(props: Partial = {}, formDefaultValues?: Partial { isLoading: false, }); - (getResourceStats as jest.Mock).mockReturnValue({ + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + mockUseResourceStats.mockReturnValue({ fileCount: 0, resourceCount: 0, - resourceCountString: '', + resourceCountString: 'Empty', + fileCountString: 'Empty', + isLoading: false, + requiresMigration: false, + shouldSkipSync: true, }); (useModeOptions as jest.Mock).mockReturnValue([ @@ -115,6 +122,17 @@ describe('BootstrapStep', () => { isLoading: true, }); + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + mockUseResourceStats.mockReturnValue({ + fileCount: 0, + resourceCount: 0, + resourceCountString: 'Empty', + fileCountString: 'Empty', + isLoading: true, + requiresMigration: false, + shouldSkipSync: true, + }); + setup(); expect(screen.getByText('Loading resource information...')).toBeInTheDocument(); @@ -139,10 +157,15 @@ describe('BootstrapStep', () => { isLoading: false, }); - (getResourceStats as jest.Mock).mockReturnValue({ + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + mockUseResourceStats.mockReturnValue({ fileCount: 2, resourceCount: 0, - resourceCountString: '', + resourceCountString: 'Empty', + fileCountString: '2 files', + isLoading: false, + requiresMigration: false, + shouldSkipSync: false, }); setup(); @@ -161,10 +184,15 @@ describe('BootstrapStep', () => { isLoading: false, }); - (getResourceStats as jest.Mock).mockReturnValue({ + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + mockUseResourceStats.mockReturnValue({ fileCount: 0, resourceCount: 7, resourceCountString: '7 resources', + fileCountString: 'Empty', + isLoading: false, + requiresMigration: true, + shouldSkipSync: false, }); setup(); @@ -173,17 +201,16 @@ describe('BootstrapStep', () => { }); }); - describe('option selection', () => { - it('should call onOptionSelect with correct argument when no migration needed', async () => { - const { props } = setup(); + describe('hook integration', () => { + it('should use useResourceStats hook correctly', async () => { + setup(); - await waitFor(() => { - expect(props.onOptionSelect).toHaveBeenCalledWith(false); - }); + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + expect(mockUseResourceStats).toHaveBeenCalledWith('test-repo', undefined); }); - it('should call onOptionSelect with true when legacy storage exists', async () => { - const { props } = setup({ + it('should use useResourceStats hook with legacy storage flag', async () => { + setup({ settingsData: { legacyStorage: true, items: [], @@ -191,30 +218,8 @@ describe('BootstrapStep', () => { }, }); - await waitFor(() => { - expect(props.onOptionSelect).toHaveBeenCalledWith(true); - }); - }); - - it('should call onOptionSelect with true when resources exist', async () => { - (useGetResourceStatsQuery as jest.Mock).mockReturnValue({ - data: { - instance: [{ group: 'dashboard.grafana.app', count: 1 }], - }, - isLoading: false, - }); - - (getResourceStats as jest.Mock).mockReturnValue({ - fileCount: 0, - resourceCount: 1, - resourceCountString: '1 resource', - }); - - const { props } = setup(); - - await waitFor(() => { - expect(props.onOptionSelect).toHaveBeenCalledWith(true); - }); + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + expect(mockUseResourceStats).toHaveBeenCalledWith('test-repo', true); }); }); diff --git a/public/app/features/provisioning/Wizard/BootstrapStep.tsx b/public/app/features/provisioning/Wizard/BootstrapStep.tsx index 85135db960f..642ba1fc430 100644 --- a/public/app/features/provisioning/Wizard/BootstrapStep.tsx +++ b/public/app/features/provisioning/Wizard/BootstrapStep.tsx @@ -1,25 +1,22 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { Trans, t } from '@grafana/i18n'; import { Box, Card, Field, Input, LoadingPlaceholder, Stack, Text } from '@grafana/ui'; -import { - RepositoryViewList, - useGetRepositoryFilesQuery, - useGetResourceStatsQuery, -} from 'app/api/clients/provisioning/v0alpha1'; +import { RepositoryViewList } from 'app/api/clients/provisioning/v0alpha1'; +import { generateRepositoryTitle } from 'app/features/provisioning/utils/data'; import { useStepStatus } from './StepStatusContext'; -import { getResourceStats, useModeOptions } from './actions'; +import { useModeOptions } from './hooks/useModeOptions'; +import { useResourceStats } from './hooks/useResourceStats'; import { WizardFormData } from './types'; export interface Props { - onOptionSelect: (requiresMigration: boolean) => void; settingsData?: RepositoryViewList; repoName: string; } -export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props) { +export function BootstrapStep({ settingsData, repoName }: Props) { const { setStepStatusInfo } = useStepStatus(); const { register, @@ -30,30 +27,16 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props) formState: { errors }, } = useFormContext(); - const resourceStats = useGetResourceStatsQuery(); - const filesQuery = useGetRepositoryFilesQuery({ name: repoName }); const selectedTarget = watch('repository.sync.target'); const options = useModeOptions(repoName, settingsData); const { target } = options[0]; - const { resourceCount, resourceCountString, fileCount } = useMemo( - () => getResourceStats(filesQuery.data, resourceStats.data), - [filesQuery.data, resourceStats.data] - ); - const requiresMigration = settingsData?.legacyStorage || resourceCount > 0; - const isLoading = resourceStats.isLoading || filesQuery.isLoading; + const { resourceCountString, fileCountString, isLoading } = useResourceStats(repoName, settingsData?.legacyStorage); useEffect(() => { // Pick a name nice name based on type+settings const repository = getValues('repository'); - switch (repository.type) { - case 'github': - const name = repository.url ?? 'github'; - setValue('repository.title', name.replace('https://github.com/', '')); - break; - case 'local': - setValue('repository.title', repository.path ?? 'local'); - break; - } + const title = generateRepositoryTitle(repository); + setValue('repository.title', title); }, [getValues, setValue]); useEffect(() => { @@ -62,8 +45,7 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props) useEffect(() => { setValue('repository.sync.target', target); - onOptionSelect(requiresMigration); - }, [target, setValue, onOptionSelect, requiresMigration]); + }, [target, setValue]); if (isLoading) { return ( @@ -85,20 +67,14 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName }: Props) Grafana instance - - {resourceCount > 0 ? resourceCountString : t('provisioning.bootstrap-step.empty', 'Empty')} - + {resourceCountString} External storage - - {fileCount > 0 - ? t('provisioning.bootstrap-step.files-count', '{{count}} files', { count: fileCount }) - : t('provisioning.bootstrap-step.empty', 'Empty')} - + {fileCountString} diff --git a/public/app/features/provisioning/Wizard/ConnectPage.tsx b/public/app/features/provisioning/Wizard/ConnectPage.tsx index 3d6206566f6..07456f5acb5 100644 --- a/public/app/features/provisioning/Wizard/ConnectPage.tsx +++ b/public/app/features/provisioning/Wizard/ConnectPage.tsx @@ -3,6 +3,8 @@ import { useParams } from 'react-router-dom-v5-compat'; import { t } from '@grafana/i18n'; import { Page } from 'app/core/components/Page/Page'; +import { isGitProvider } from '../utils/repositoryTypes'; + import { ProvisioningWizard } from './ProvisioningWizard'; import { StepStatusProvider } from './StepStatusContext'; import { RepoType } from './types'; @@ -18,7 +20,7 @@ export default function ConnectPage() { - {isGithub && ( + + {/*TODO: Add same permission info for other providers*/} + {type === 'github' && } + + {gitFields && ( <> - { - return ( - { - setValue('repository.token', ''); - setTokenConfigured(false); - }} - /> - ); - }} + rules={gitFields.tokenConfig.validation} + render={({ field: { ref, ...field } }) => ( + { + setValue('repository.token', ''); + setTokenConfigured(false); + }} + /> + )} /> + {gitFields.tokenUserConfig && ( + + + + )} + - + )} - {type === 'local' && ( + {localFields && ( )} diff --git a/public/app/features/provisioning/Wizard/FinishStep.tsx b/public/app/features/provisioning/Wizard/FinishStep.tsx index 9c3e4531ff0..77c9e06aab2 100644 --- a/public/app/features/provisioning/Wizard/FinishStep.tsx +++ b/public/app/features/provisioning/Wizard/FinishStep.tsx @@ -4,15 +4,19 @@ import { useFormContext } from 'react-hook-form'; import { Trans, t } from '@grafana/i18n'; import { Checkbox, Field, Input, Stack, Text, TextLink } from '@grafana/ui'; -import { checkPublicAccess, checkImageRenderer } from '../GettingStarted/features'; +import { checkImageRenderer, checkPublicAccess } from '../GettingStarted/features'; +import { isGitProvider } from '../utils/repositoryTypes'; +import { getGitProviderFields } from './fields'; import { WizardFormData } from './types'; export function FinishStep() { const { register, watch, setValue } = useFormContext(); const [type, readOnly] = watch(['repository.type', 'repository.readOnly']); + const isGithub = type === 'github'; + const isGitBased = isGitProvider(type); const isPublic = checkPublicAccess(); const hasImageRenderer = checkImageRenderer(); @@ -21,29 +25,34 @@ export function FinishStep() { setValue('repository.sync.enabled', true); }, [setValue]); + // Get field configurations for git-based providers + const gitFields = isGitBased ? getGitProviderFields(type) : null; + return ( - - {isGithub && ( + + {isGitBased && ( )} - + { @@ -60,63 +69,46 @@ export function FinishStep() { /> - {isGithub && ( - <> - - - Allows users to choose whether to open a pull request when saving changes. If the repository does not - allow direct changes to the main branch, a pull request may still be required. - - } - /> - + {gitFields && ( + + + + )} - - - - Enhance your GitHub experience - - - You can always set this up later - - - - + + + Create preview links for pull requests + + {(!isPublic || !hasImageRenderer) && ( <> - - Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards - will be shared in your Git repository and visible to anyone with repository access. - {' '} - - - Requires image rendering.{' '} - - Set up image rendering - + {' '} + + + (requires{' '} + + image rendering + {' '} + and public access enabled) - } - {...register('repository.generateDashboardPreviews')} - /> - - - + )} + + } + disabled={!isPublic || !hasImageRenderer} + /> + )} ); diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx new file mode 100644 index 00000000000..83fc75ddfb9 --- /dev/null +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx @@ -0,0 +1,572 @@ +import { QueryStatus } from '@reduxjs/toolkit/query'; +import { screen, waitFor } from '@testing-library/react'; +import { UserEvent } from '@testing-library/user-event'; +import { render } from 'test/test-utils'; + +import { + useCreateRepositoryJobsMutation, + useGetFrontendSettingsQuery, + useGetRepositoryFilesQuery, + useGetResourceStatsQuery, +} from 'app/api/clients/provisioning/v0alpha1'; + +import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository'; + +import { ProvisioningWizard } from './ProvisioningWizard'; +import { StepStatusProvider } from './StepStatusContext'; + +const mockNavigate = jest.fn(); + +jest.mock('react-router-dom-v5-compat', () => ({ + ...jest.requireActual('react-router-dom-v5-compat'), + useNavigate: () => mockNavigate, +})); + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getAppEvents: () => ({ + publish: jest.fn(), + }), +})); + +jest.mock('../hooks/useCreateOrUpdateRepository'); +jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ + ...jest.requireActual('app/api/clients/provisioning/v0alpha1'), + useGetFrontendSettingsQuery: jest.fn(), + useGetRepositoryFilesQuery: jest.fn(), + useGetResourceStatsQuery: jest.fn(), + useCreateRepositoryJobsMutation: jest.fn(), +})); + +const mockUseCreateOrUpdateRepository = useCreateOrUpdateRepository as jest.MockedFunction< + typeof useCreateOrUpdateRepository +>; +const mockUseGetFrontendSettingsQuery = useGetFrontendSettingsQuery as jest.MockedFunction< + typeof useGetFrontendSettingsQuery +>; +const mockUseGetRepositoryFilesQuery = useGetRepositoryFilesQuery as jest.MockedFunction< + typeof useGetRepositoryFilesQuery +>; +const mockUseGetResourceStatsQuery = useGetResourceStatsQuery as jest.MockedFunction; +const mockUseCreateRepositoryJobsMutation = useCreateRepositoryJobsMutation as jest.MockedFunction< + typeof useCreateRepositoryJobsMutation +>; + +function setup(jsx: JSX.Element) { + return render({jsx}); +} + +async function typeIntoTokenField(user: UserEvent, placeholder: string, value: string) { + const resetButton = screen.queryByRole('button', { name: /Reset/i }); + if (resetButton) { + await user.click(resetButton); + } + await user.type(screen.getByPlaceholderText(placeholder), value); +} + +async function fillConnectionForm( + user: UserEvent, + type: 'github' | 'gitlab' | 'bitbucket' | 'local' | 'git', + data: { + token?: string; + tokenUser?: string; + url?: string; + branch?: string; + path?: string; + } +) { + if (type !== 'local' && data.token) { + const tokenPlaceholders = { + github: 'ghp_xxxxxxxxxxxxxxxxxxxx', + gitlab: 'glpat-xxxxxxxxxxxxxxxxxxxx', + bitbucket: 'ATBBxxxxxxxxxxxxxxxx', + git: 'token or password', + }; + await typeIntoTokenField(user, tokenPlaceholders[type], data.token); + } + + if ((type === 'bitbucket' || type === 'git') && data.tokenUser) { + await user.type(screen.getByPlaceholderText('username'), data.tokenUser); + } + + if (type !== 'local' && data.url) { + await user.type(screen.getByRole('textbox', { name: /Repository URL/i }), data.url); + } + + if (type !== 'local' && data.branch) { + await user.type(screen.getByRole('textbox', { name: /Branch/i }), data.branch); + } + + if (data.path) { + await user.type(screen.getByRole('textbox', { name: /Path/i }), data.path); + } +} + +describe('ProvisioningWizard', () => { + beforeEach(() => { + jest.clearAllMocks(); + + mockUseGetFrontendSettingsQuery.mockReturnValue({ + data: { + items: [], + legacyStorage: false, + availableRepositoryTypes: ['github', 'gitlab', 'bitbucket', 'git', 'local'], + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); + + mockUseGetRepositoryFilesQuery.mockReturnValue({ + data: [], + isLoading: false, + error: null, + refetch: jest.fn(), + }); + + mockUseGetResourceStatsQuery.mockReturnValue({ + data: { + instance: [], + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); + + const mockCreateJob = jest.fn(); + mockUseCreateRepositoryJobsMutation.mockReturnValue([ + mockCreateJob, + { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + data: undefined, + isUninitialized: true, + isSuccess: false, + isError: false, + reset: jest.fn(), + }, + ]); + + const mockSubmitData = jest.fn(); + const mockMutationState = { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + data: undefined, + isUninitialized: true, + isSuccess: false, + isError: false, + reset: jest.fn(), + }; + (mockUseCreateOrUpdateRepository as jest.Mock).mockReturnValue([ + mockSubmitData, + mockMutationState, + mockMutationState, + ]); + + mockSubmitData.mockResolvedValue({ + data: { + metadata: { + name: 'test-repo-abc123', + }, + spec: { + type: 'github', + title: 'Test Repository', + }, + }, + }); + }); + + describe('Happy Path', () => { + it('should render connection step initially', async () => { + setup(); + + expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument(); + expect(screen.getByText('Personal Access Token *')).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Repository URL/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Branch/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Path/i })).toBeInTheDocument(); + }); + + it('should progress through first 3 steps successfully', async () => { + mockUseGetResourceStatsQuery.mockReturnValue({ + data: { + instance: [{ group: 'dashboard.grafana.app', count: 1 }], + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument(); + }); + + expect(mockUseCreateOrUpdateRepository).toHaveBeenCalled(); + + await user.click(screen.getByRole('button', { name: /Synchronize with external storage/i })); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: /3\. Synchronize with external storage/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Begin synchronization/i })).toBeInTheDocument(); + }); + }); + + it('should skip sync step when there are no resources', async () => { + mockUseGetResourceStatsQuery.mockReturnValue({ + data: { + instance: [], // No resources + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); + + mockUseGetRepositoryFilesQuery.mockReturnValue({ + data: { + items: [], // No files + }, + isLoading: false, + error: null, + refetch: jest.fn(), + } as ReturnType); + + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument(); + }); + + // Should show "Choose additional settings" button instead of "Synchronize with external storage" + expect(screen.getByRole('button', { name: /Choose additional settings/i })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /Synchronize with external storage/i })).not.toBeInTheDocument(); + + // Verify that the sync step (step 3) would be skipped in the button text logic + const nextButton = screen.getByRole('button', { name: /Choose additional settings/i }); + expect(nextButton).toBeInTheDocument(); + }); + }); + + describe('Error Handling', () => { + it('should show field errors when connection test fails with TestResults error', async () => { + const mockSubmitData = jest.fn(); + const mockMutationState = { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + }; + (mockUseCreateOrUpdateRepository as jest.Mock).mockReturnValue([ + mockSubmitData, + mockMutationState, + mockMutationState, + ]); + + const testResultsError = { + data: { + kind: 'TestResults', + apiVersion: 'provisioning.grafana.app/v0alpha1', + success: false, + code: 400, + errors: [ + { + type: 'FieldValueInvalid', + field: 'spec.github.branch', + detail: 'Branch "invalid-branch" not found', + }, + ], + }, + status: 400, + }; + + mockSubmitData.mockRejectedValue(testResultsError); + + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'invalid-branch', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByText('Branch "invalid-branch" not found')).toBeInTheDocument(); + }); + + expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument(); + }); + + it('should show error alert for Status API errors', async () => { + const mockSubmitData = jest.fn(); + const mockMutationState = { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + }; + (mockUseCreateOrUpdateRepository as jest.Mock).mockReturnValue([ + mockSubmitData, + mockMutationState, + mockMutationState, + ]); + + const statusError = new Error('decrypt gitlab token: not found'); + mockSubmitData.mockRejectedValue(statusError); + + const { user } = setup(); + + await fillConnectionForm(user, 'gitlab', { + token: 'invalid-token', + url: 'https://gitlab.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByText('Repository connection failed')).toBeInTheDocument(); + }); + + expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument(); + }); + + it('should show error when repository creation fails', async () => { + const mockSubmitData = jest.fn(); + const mockMutationState = { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + }; + (mockUseCreateOrUpdateRepository as jest.Mock).mockReturnValue([ + mockSubmitData, + mockMutationState, + mockMutationState, + ]); + + mockSubmitData.mockResolvedValue({ + error: { + kind: 'Status', + status: 'Failure', + message: 'Repository creation failed', + code: 500, + }, + }); + + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByText('Repository request failed')).toBeInTheDocument(); + }); + }); + }); + + describe('Navigation and State', () => { + it('should handle cancel on first step', async () => { + const { user } = setup(); + + await user.click(screen.getByRole('button', { name: /Cancel/i })); + + expect(mockNavigate).toHaveBeenCalledWith('/admin/provisioning'); + }); + + it('should handle cancel on subsequent steps with repository deletion', async () => { + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument(); + }); + + await user.click(screen.getByRole('button', { name: /Cancel/i })); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith('/admin/provisioning'); + }); + }); + + it('should disable next button when submitting', async () => { + const mockSubmitData = jest.fn(); + const mockMutationState = { + status: QueryStatus.uninitialized, + isLoading: false, + error: null, + data: undefined, + isUninitialized: true, + isSuccess: false, + isError: false, + reset: jest.fn(), + }; + + (mockUseCreateOrUpdateRepository as jest.Mock).mockReturnValue([ + mockSubmitData, + mockMutationState, + mockMutationState, + ]); + + mockSubmitData.mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 100))); + + const { user } = setup(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + expect(screen.getByRole('button', { name: /Submitting.../i })).toBeDisabled(); + }); + }); + + describe('Form Validation', () => { + it('should validate required fields', async () => { + const { user } = setup(); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument(); + }); + + it('should show button text changes based on current step', async () => { + // Mock resources to ensure sync step is not skipped + mockUseGetResourceStatsQuery.mockReturnValue({ + data: { + instance: [{ group: 'dashboard.grafana.app', count: 1 }], + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); + + const { user } = setup(); + + expect(screen.getByRole('button', { name: /Choose what to synchronize/i })).toBeInTheDocument(); + + await fillConnectionForm(user, 'github', { + token: 'test-token', + url: 'https://github.com/test/repo', + branch: 'main', + path: '/', + }); + + await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /Synchronize with external storage/i })).toBeInTheDocument(); + }); + }); + }); + + describe('Different Repository Types', () => { + it('should render GitLab-specific fields', async () => { + setup(); + + expect(screen.getByText('Project Access Token *')).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Repository URL/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Branch/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Path/i })).toBeInTheDocument(); + }); + + it('should render Bitbucket-specific fields', async () => { + setup(); + + expect(screen.getByText('App Password *')).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Username/ })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Repository URL/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Branch/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Path/i })).toBeInTheDocument(); + }); + + it('should render Git-specific fields', async () => { + setup(); + + expect(screen.getByText('Access Token *')).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Username/ })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Repository URL/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Branch/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /Path/i })).toBeInTheDocument(); + }); + + it('should render local repository fields', async () => { + setup(); + + expect(screen.getByRole('textbox', { name: /Path/i })).toBeInTheDocument(); + expect(screen.queryByPlaceholderText('ghp_xxxxxxxxxxxxxxxxxxxx')).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText('glpat-xxxxxxxxxxxxxxxxxxxx')).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText('ATBBxxxxxxxxxxxxxxxx')).not.toBeInTheDocument(); + expect(screen.queryByRole('textbox', { name: /Repository URL/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('textbox', { name: /Branch/i })).not.toBeInTheDocument(); + }); + + it('should accept tokenUser input for Bitbucket provider', async () => { + const { user } = setup(); + + await fillConnectionForm(user, 'bitbucket', { + token: 'test-token', + tokenUser: 'test-user', + url: 'https://bitbucket.org/test/repo', + branch: 'main', + path: '/', + }); + + expect(screen.getByDisplayValue('test-user')).toBeInTheDocument(); + }); + + it('should accept tokenUser input for Git provider', async () => { + const { user } = setup(); + + await fillConnectionForm(user, 'git', { + token: 'test-token', + tokenUser: 'test-user', + url: 'https://git.example.com/test/repo.git', + branch: 'main', + path: '/', + }); + + expect(screen.getByDisplayValue('test-user')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx index 0eae8ed5ddb..6c9ef49e917 100644 --- a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx @@ -6,11 +6,12 @@ import { useNavigate } from 'react-router-dom-v5-compat'; import { AppEvents, GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { getAppEvents, isFetchError } from '@grafana/runtime'; -import { Alert, Box, Button, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Box, Button, Stack, Text, useStyles2 } from '@grafana/ui'; import { useDeleteRepositoryMutation, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1'; import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt'; import { getDefaultValues } from '../Config/defaults'; +import { ProvisioningAlert } from '../Shared/ProvisioningAlert'; import { PROVISIONING_URL } from '../constants'; import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository'; import { dataToSpec } from '../utils/data'; @@ -22,6 +23,8 @@ import { FinishStep } from './FinishStep'; import { useStepStatus } from './StepStatusContext'; import { Step, Stepper } from './Stepper'; import { SynchronizeStep } from './SynchronizeStep'; +import { useCreateSyncJob } from './hooks/useCreateSyncJob'; +import { useResourceStats } from './hooks/useResourceStats'; import { RepoType, WizardFormData, WizardStep } from './types'; const appEvents = getAppEvents(); @@ -58,14 +61,15 @@ const getSteps = (): Array> => { export function ProvisioningWizard({ type }: { type: RepoType }) { const [activeStep, setActiveStep] = useState('connection'); const [completedSteps, setCompletedSteps] = useState([]); - const [requiresMigration, setRequiresMigration] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isCancelling, setIsCancelling] = useState(false); - const { stepStatusInfo, setStepStatusInfo, isStepSuccess, isStepRunning, hasStepError } = useStepStatus(); + const { stepStatusInfo, setStepStatusInfo, isStepSuccess, isStepRunning, hasStepError, hasStepWarning } = + useStepStatus(); - const settingsQuery = useGetFrontendSettingsQuery(); + const { data } = useGetFrontendSettingsQuery(); + const isLegacyStorage = Boolean(data?.legacyStorage); const navigate = useNavigate(); const steps = getSteps(); @@ -91,16 +95,24 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { handleSubmit, } = methods; - const repoName = watch('repositoryName'); + const [repoName = '', repoType] = watch(['repositoryName', 'repository.type']); const [submitData] = useCreateOrUpdateRepository(repoName); const [deleteRepository] = useDeleteRepositoryMutation(); + const { shouldSkipSync, requiresMigration } = useResourceStats(repoName, isLegacyStorage); + const { createSyncJob, isLoading: isCreatingSkipJob } = useCreateSyncJob({ + repoName: repoName, + requiresMigration, + repoType, + isLegacyStorage, + setStepStatusInfo, + }); const currentStepIndex = steps.findIndex((s) => s.id === activeStep); const currentStepConfig = steps[currentStepIndex]; // A different repository is marked with instance target -- nothing will succeed useEffect(() => { - if (settingsQuery.data?.items.some((item) => item.target === 'instance' && item.name !== repoName)) { + if (data?.items.some((item) => item.target === 'instance' && item.name !== repoName)) { appEvents.publish({ type: AppEvents.alertError.name, payload: [ @@ -110,7 +122,7 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { navigate(PROVISIONING_URL); } - }, [navigate, repoName, settingsQuery.data?.items]); + }, [navigate, repoName, data?.items]); const handleRepositoryDeletion = async (name: string) => { try { @@ -144,9 +156,18 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { return t('provisioning.wizard.button-next', 'Finish'); } + // If on bootstrap step and should skip sync, show finish step name + if (currentStep === 'bootstrap' && shouldSkipSync) { + const finishStepIndex = stepIndex + 2; + if (finishStepIndex < steps.length) { + return steps[finishStepIndex].name; + } + return t('provisioning.wizard.button-next', 'Finish'); + } + return steps[stepIndex + 1].name; }, - [steps] + [steps, shouldSkipSync] ); const handleNext = async () => { @@ -156,7 +177,27 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { if (isLastStep) { navigate(PROVISIONING_URL); } else { - setActiveStep(steps[currentStepIndex + 1].id); + let nextStepIndex = currentStepIndex + 1; + + // Skip synchronize step if no sync is needed + if (activeStep === 'bootstrap' && shouldSkipSync) { + nextStepIndex = currentStepIndex + 2; // Skip to finish step + + // Create a pull job to initialize the repository + if (repoName) { + const job = await createSyncJob(); + if (!job) { + return; // Don't proceed if job creation fails + } + } + } + + if (nextStepIndex >= steps.length) { + navigate(PROVISIONING_URL); + return; + } + + setActiveStep(steps[nextStepIndex].id); setCompletedSteps((prev) => [...new Set([...prev, activeStep])]); setStepStatusInfo({ status: 'idle' }); } @@ -165,11 +206,12 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { const onSubmit = async () => { if (currentStepConfig?.submitOnNext) { // Validate form data before proceeding - if (activeStep === 'connection' || activeStep === 'bootstrap') { - const isValid = await trigger(['repository', 'repository.title']); - if (!isValid) { - return; - } + const fieldsToValidate = + activeStep === 'connection' ? (['repository'] as const) : (['repository', 'repository.title'] as const); + + const isValid = await trigger(fieldsToValidate); + if (!isValid) { + return; } setIsSubmitting(true); @@ -210,8 +252,8 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { setIsSubmitting(false); } } else { - // only proceed if the job was successful - if (isStepSuccess) { + // proceed if the job was successful or had warnings + if (isStepSuccess || hasStepWarning) { handleNext(); } } @@ -222,11 +264,11 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { if (activeStep !== 'connection' && hasStepError) { return true; } - // Synchronize step requires success to proceed + // Synchronize step requires success or warning to proceed if (activeStep === 'synchronize') { - return !isStepSuccess; // Disable next button if the step is not successful + return !(isStepSuccess || hasStepWarning); // Disable next button if the step is not successful or has warnings } - return isSubmitting || isCancelling || isStepRunning; + return isSubmitting || isCancelling || isStepRunning || isCreatingSkipJob; }; return ( @@ -243,23 +285,13 @@ export function ProvisioningWizard({ type }: { type: RepoType }) { - {hasStepError && } + {hasStepError && 'error' in stepStatusInfo && } + {hasStepWarning && 'warning' in stepStatusInfo && }
{activeStep === 'connection' && } - {activeStep === 'bootstrap' && ( - - )} - {activeStep === 'synchronize' && ( - - )} + {activeStep === 'bootstrap' && } + {activeStep === 'synchronize' && } {activeStep === 'finish' && }
diff --git a/public/app/features/provisioning/Wizard/StepStatusContext.tsx b/public/app/features/provisioning/Wizard/StepStatusContext.tsx index e3081bfba02..efd86869979 100644 --- a/public/app/features/provisioning/Wizard/StepStatusContext.tsx +++ b/public/app/features/provisioning/Wizard/StepStatusContext.tsx @@ -11,6 +11,7 @@ interface StepStatusContextData { // Computed status checks hasStepError: boolean; + hasStepWarning: boolean; isStepRunning: boolean; isStepSuccess: boolean; isStepIdle: boolean; @@ -29,6 +30,7 @@ export const StepStatusProvider = ({ children }: PropsWithChildren) => { stepStatusInfo, setStepStatusInfo, hasStepError: stepStatusInfo.status === 'error', + hasStepWarning: stepStatusInfo.status === 'warning', isStepRunning: stepStatusInfo.status === 'running', isStepSuccess: stepStatusInfo.status === 'success', isStepIdle: stepStatusInfo.status === 'idle', diff --git a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx index 449cf3f5038..64ae9ee8732 100644 --- a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx +++ b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx @@ -3,67 +3,38 @@ import { useFormContext } from 'react-hook-form'; import { Trans, t } from '@grafana/i18n'; import { Button, Text, Stack, Alert, TextLink, Field, Checkbox } from '@grafana/ui'; -import { Job, useCreateRepositoryJobsMutation } from 'app/api/clients/provisioning/v0alpha1'; +import { Job } from 'app/api/clients/provisioning/v0alpha1'; import { JobStatus } from '../Job/JobStatus'; import { useStepStatus } from './StepStatusContext'; +import { useCreateSyncJob } from './hooks/useCreateSyncJob'; +import { useResourceStats } from './hooks/useResourceStats'; import { WizardFormData } from './types'; export interface SynchronizeStepProps { - requiresMigration: boolean; isLegacyStorage?: boolean; } -export function SynchronizeStep({ requiresMigration, isLegacyStorage }: SynchronizeStepProps) { - const { setStepStatusInfo } = useStepStatus(); - const [createJob] = useCreateRepositoryJobsMutation(); +export function SynchronizeStep({ isLegacyStorage }: SynchronizeStepProps) { const { getValues, register, watch } = useFormContext(); - const repoType = watch('repository.type'); - const supportsHistory = repoType === 'github' && isLegacyStorage; + const { setStepStatusInfo } = useStepStatus(); + const [repoName = '', repoType] = watch(['repositoryName', 'repository.type']); + const { requiresMigration } = useResourceStats(repoName, isLegacyStorage); + const { createSyncJob, supportsHistory } = useCreateSyncJob({ + repoName, + requiresMigration, + repoType, + isLegacyStorage, + setStepStatusInfo, + }); const [job, setJob] = useState(); const startSynchronization = async () => { - const [history, repoName] = getValues(['migrate.history', 'repositoryName']); - if (!repoName) { - setStepStatusInfo({ - status: 'error', - error: t('provisioning.synchronize-step.error-no-repository-name', 'No repository name provided'), - }); - return; - } - - try { - setStepStatusInfo({ status: 'running' }); - const jobSpec = requiresMigration - ? { - migrate: { - history: history && supportsHistory, - }, - } - : { - pull: { - incremental: false, // will queue a full resync job - }, - }; - - const response = await createJob({ - name: repoName, - jobSpec, - }).unwrap(); - - if (!response?.metadata?.name) { - return setStepStatusInfo({ - status: 'error', - error: t('provisioning.synchronize-step.error-no-job-id', 'Failed to start job'), - }); - } + const [history] = getValues(['migrate.history']); + const response = await createSyncJob({ history }); + if (response) { setJob(response); - } catch (error) { - setStepStatusInfo({ - status: 'error', - error: t('provisioning.synchronize-step.error-starting-job', 'Error starting job'), - }); } }; diff --git a/public/app/features/provisioning/Wizard/fields.ts b/public/app/features/provisioning/Wizard/fields.ts new file mode 100644 index 00000000000..7abe995cc1a --- /dev/null +++ b/public/app/features/provisioning/Wizard/fields.ts @@ -0,0 +1,337 @@ +import { t } from '@grafana/i18n'; + +import { RepoType } from './types'; + +export interface FieldConfig { + label: string; + description?: string; + placeholder?: string; + required?: boolean; + validation?: { + required?: string | boolean; + pattern?: { + value: RegExp; + message: string; + }; + }; +} + +// Provider-specific field configurations for all providers +// This needs to be a function for translations to work +const getProviderConfigs = (): Record> => ({ + github: { + token: { + label: t('provisioning.github.token-label', 'Personal Access Token'), + description: t( + 'provisioning.github.token-description', + 'GitHub Personal Access Token with repository permissions' + ), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'ghp_xxxxxxxxxxxxxxxxxxxx', + required: true, + validation: { + required: t('provisioning.github.token-required', 'GitHub token is required'), + }, + }, + url: { + label: t('provisioning.github.url-label', 'Repository URL'), + description: t('provisioning.github.url-description', 'The GitHub repository URL'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'https://github.com/owner/repository', + required: true, + validation: { + required: t('provisioning.github.url-required', 'Repository URL is required'), + pattern: { + value: /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/, + message: t('provisioning.github.url-pattern', 'Must be a valid GitHub repository URL'), + }, + }, + }, + branch: { + label: t('provisioning.github.branch-label', 'Branch'), + description: t('provisioning.github.branch-description', 'The branch to use for provisioning'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'main', + required: true, + validation: { + required: t('provisioning.github.branch-required', 'Branch is required'), + }, + }, + path: { + label: t('provisioning.github.path-label', 'Path'), + description: t('provisioning.github.path-description', 'Optional subdirectory path within the repository'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'grafana/', + required: false, + }, + prWorkflow: { + label: t('provisioning.github.pr-workflow-label', 'Enable pull request option when saving'), + description: t( + 'provisioning.github.pr-workflow-description', + 'Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.' + ), + }, + }, + gitlab: { + token: { + label: t('provisioning.gitlab.token-label', 'Project Access Token'), + description: t( + 'provisioning.gitlab.token-description', + 'GitLab Project Access Token with repository permissions' + ), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'glpat-xxxxxxxxxxxxxxxxxxxx', + required: true, + validation: { + required: t('provisioning.gitlab.token-required', 'GitLab token is required'), + }, + }, + url: { + label: t('provisioning.gitlab.url-label', 'Repository URL'), + description: t('provisioning.gitlab.url-description', 'The GitLab repository URL'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'https://gitlab.com/owner/repository', + required: true, + validation: { + required: t('provisioning.gitlab.url-required', 'Repository URL is required'), + pattern: { + value: /^https:\/\/gitlab\.com\/[^\/]+\/[^\/]+\/?$/, + message: t('provisioning.gitlab.url-pattern', 'Must be a valid GitLab repository URL'), + }, + }, + }, + branch: { + label: t('provisioning.gitlab.branch-label', 'Branch'), + description: t('provisioning.gitlab.branch-description', 'The branch to use for provisioning'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'main', + required: true, + validation: { + required: t('provisioning.gitlab.branch-required', 'Branch is required'), + }, + }, + path: { + label: t('provisioning.gitlab.path-label', 'Path'), + description: t('provisioning.gitlab.path-description', 'Optional subdirectory path within the repository'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'grafana/', + required: false, + }, + prWorkflow: { + label: t('provisioning.gitlab.pr-workflow-label', 'Enable merge request option when saving'), + description: t( + 'provisioning.gitlab.pr-workflow-description', + 'Allows users to choose whether to open a merge request when saving changes. If the repository does not allow direct changes to the main branch, a merge request may still be required.' + ), + }, + }, + bitbucket: { + token: { + label: t('provisioning.bitbucket.token-label', 'App Password'), + description: t('provisioning.bitbucket.token-description', 'Bitbucket App Password with repository permissions'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'ATBBxxxxxxxxxxxxxxxx', + required: true, + validation: { + required: t('provisioning.bitbucket.token-required', 'Bitbucket token is required'), + }, + }, + tokenUser: { + label: t('provisioning.bitbucket.token-user-label', 'Username'), + description: t( + 'provisioning.bitbucket.token-user-description', + 'The username that will be used to access the repository with the app password' + ), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'username', + required: true, + validation: { + required: t('provisioning.bitbucket.token-user-required', 'Username is required'), + }, + }, + url: { + label: t('provisioning.bitbucket.url-label', 'Repository URL'), + description: t('provisioning.bitbucket.url-description', 'The Bitbucket repository URL'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'https://bitbucket.org/owner/repository', + required: true, + validation: { + required: t('provisioning.bitbucket.url-required', 'Repository URL is required'), + pattern: { + value: /^https:\/\/bitbucket\.org\/[^\/]+\/[^\/]+\/?$/, + message: t('provisioning.bitbucket.url-pattern', 'Must be a valid Bitbucket repository URL'), + }, + }, + }, + branch: { + label: t('provisioning.bitbucket.branch-label', 'Branch'), + description: t('provisioning.bitbucket.branch-description', 'The branch to use for provisioning'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'main', + required: true, + validation: { + required: t('provisioning.bitbucket.branch-required', 'Branch is required'), + }, + }, + path: { + label: t('provisioning.bitbucket.path-label', 'Path'), + description: t('provisioning.bitbucket.path-description', 'Optional subdirectory path within the repository'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'grafana/', + required: false, + }, + prWorkflow: { + label: t('provisioning.bitbucket.pr-workflow-label', 'Enable pull request option when saving'), + description: t( + 'provisioning.bitbucket.pr-workflow-description', + 'Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.' + ), + }, + }, + git: { + token: { + label: t('provisioning.git.token-label', 'Access Token'), + description: t('provisioning.git.token-description', 'Git repository access token or password'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'token or password', + required: true, + validation: { + required: t('provisioning.git.token-required', 'Git token is required'), + }, + }, + tokenUser: { + label: t('provisioning.git.token-user-label', 'Username'), + description: t( + 'provisioning.git.token-user-description', + 'The username that will be used to access the repository with the access token' + ), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'username', + required: true, + validation: { + required: t('provisioning.git.token-user-required', 'Username is required'), + }, + }, + url: { + label: t('provisioning.git.url-label', 'Repository URL'), + description: t('provisioning.git.url-description', 'The Git repository URL'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'https://git.example.com/owner/repository.git', + required: true, + validation: { + required: t('provisioning.git.url-required', 'Repository URL is required'), + pattern: { + value: /^https?:\/\/.+/, + message: t('provisioning.git.url-pattern', 'Must be a valid Git repository URL'), + }, + }, + }, + branch: { + label: t('provisioning.git.branch-label', 'Branch'), + description: t('provisioning.git.branch-description', 'The branch to use for provisioning'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'main', + required: true, + validation: { + required: t('provisioning.git.branch-required', 'Branch is required'), + }, + }, + path: { + label: t('provisioning.git.path-label', 'Path'), + description: t('provisioning.git.path-description', 'Optional subdirectory path within the repository'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: 'grafana/', + required: false, + }, + prWorkflow: { + label: t('provisioning.git.pr-workflow-label', 'Enable pull request option when saving'), + description: t( + 'provisioning.git.pr-workflow-description', + 'Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.' + ), + }, + }, + local: { + path: { + label: t('provisioning.local.path-label', 'Repository Path'), + description: t('provisioning.local.path-description', 'Local file system path to the repository'), + // eslint-disable-next-line @grafana/i18n/no-untranslated-strings + placeholder: '/path/to/repository', + required: true, + validation: { + required: t('provisioning.local.path-required', 'Repository path is required'), + }, + }, + }, +}); + +/** + * Get git provider field configurations that are guaranteed to exist + * This should only be called for git-based providers + */ +export const getGitProviderFields = ( + type: RepoType +): + | { + tokenConfig: FieldConfig; + tokenUserConfig?: FieldConfig; + urlConfig: FieldConfig; + branchConfig: FieldConfig; + pathConfig: FieldConfig; + prWorkflowConfig: FieldConfig; + } + | undefined => { + const configs = getProviderConfigs()[type]; + if (!configs) { + throw new Error(`No configuration found for repository type: ${type}`); + } + + // For git providers, these fields are guaranteed to exist + const tokenConfig = configs.token; + const tokenUserConfig = configs.tokenUser; // Optional field, only for some providers + const urlConfig = configs.url; + const branchConfig = configs.branch; + const pathConfig = configs.path; + const prWorkflowConfig = configs.prWorkflow; + + if (!tokenConfig || !urlConfig || !branchConfig || !pathConfig || !prWorkflowConfig) { + throw new Error(`Missing required field configurations for ${type}`); + } + + return { + tokenConfig, + tokenUserConfig, + urlConfig, + branchConfig, + pathConfig, + prWorkflowConfig, + }; +}; + +/** + * Get local provider field configurations that are guaranteed to exist + * This should only be called for local providers + */ +export const getLocalProviderFields = ( + type: RepoType +): + | { + pathConfig: FieldConfig; + } + | undefined => { + const configs = getProviderConfigs()[type]; + if (!configs) { + throw new Error(`No configuration found for repository type: ${type}`); + } + + // For local providers, the path field is guaranteed to exist + const pathConfig = configs.path; + + if (!pathConfig) { + throw new Error(`Missing required field configuration for ${type}: path`); + } + + return { + pathConfig, + }; +}; diff --git a/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts new file mode 100644 index 00000000000..94b457bf97e --- /dev/null +++ b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts @@ -0,0 +1,78 @@ +import { t } from '@grafana/i18n'; +import { useCreateRepositoryJobsMutation } from 'app/api/clients/provisioning/v0alpha1'; + +import { isGitProvider } from '../../utils/repositoryTypes'; +import { RepoType, StepStatusInfo } from '../types'; + +export interface UseCreateSyncJobParams { + repoName: string; + requiresMigration: boolean; + repoType: RepoType; + isLegacyStorage?: boolean; + setStepStatusInfo?: (info: StepStatusInfo) => void; +} + +export function useCreateSyncJob({ + repoName, + requiresMigration, + repoType, + isLegacyStorage, + setStepStatusInfo, +}: UseCreateSyncJobParams) { + const [createJob, { isLoading }] = useCreateRepositoryJobsMutation(); + const supportsHistory = isGitProvider(repoType) && isLegacyStorage; + + const createSyncJob = async (options?: { history?: boolean }) => { + if (!repoName) { + setStepStatusInfo?.({ + status: 'error', + error: t('provisioning.sync-job.error-no-repository-name', 'No repository name provided'), + }); + return null; + } + + try { + setStepStatusInfo?.({ status: 'running' }); + + const jobSpec = requiresMigration + ? { + migrate: { + history: (options?.history || false) && supportsHistory, + }, + } + : { + pull: { + incremental: false, + }, + }; + + const response = await createJob({ + name: repoName, + jobSpec, + }).unwrap(); + + if (!response?.metadata?.name) { + setStepStatusInfo?.({ + status: 'error', + error: t('provisioning.sync-job.error-no-job-id', 'Failed to start job'), + }); + return null; + } + + setStepStatusInfo?.({ status: 'success' }); + return response; + } catch (error) { + setStepStatusInfo?.({ + status: 'error', + error: t('provisioning.sync-job.error-starting-job', 'Error starting job'), + }); + return null; + } + }; + + return { + createSyncJob, + isLoading, + supportsHistory, + }; +} diff --git a/public/app/features/provisioning/Wizard/actions.ts b/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts similarity index 70% rename from public/app/features/provisioning/Wizard/actions.ts rename to public/app/features/provisioning/Wizard/hooks/useModeOptions.ts index 5c0340edc45..a70f190b508 100644 --- a/public/app/features/provisioning/Wizard/actions.ts +++ b/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts @@ -1,13 +1,9 @@ import { useMemo } from 'react'; import { t } from '@grafana/i18n'; -import { - GetRepositoryFilesApiResponse, - GetResourceStatsApiResponse, - RepositoryViewList, -} from 'app/api/clients/provisioning/v0alpha1'; +import { RepositoryViewList } from 'app/api/clients/provisioning/v0alpha1'; -import { ModeOption } from './types'; +import { ModeOption } from '../types'; /** * Filters available mode options based on system state @@ -68,34 +64,3 @@ export function useModeOptions(repoName: string, settings?: RepositoryViewList) return filterModeOptions(modeOptions, repoName, settings); }, [repoName, settings]); } - -export function getResourceStats(files?: GetRepositoryFilesApiResponse, stats?: GetResourceStatsApiResponse) { - const fileCount = - files?.items?.reduce((count, file) => { - const path = file.path ?? ''; - return path.endsWith('.json') || path.endsWith('.yaml') ? count + 1 : count; - }, 0) ?? 0; - - let counts: string[] = []; - let resourceCount = 0; - - stats?.instance?.forEach((stat) => { - switch (stat.group) { - case 'folders': - case 'folder.grafana.app': - resourceCount += stat.count; - counts.push(`${stat.count} ${stat.count > 1 ? 'folders' : 'folder'}`); - break; - case 'dashboard.grafana.app': - resourceCount += stat.count; - counts.push(`${stat.count} ${stat.count > 1 ? 'dashboards' : 'dashboard'}`); - break; - } - }); - - return { - fileCount, - resourceCount, - resourceCountString: counts.join(',\n'), - }; -} diff --git a/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts new file mode 100644 index 00000000000..03d259303b8 --- /dev/null +++ b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts @@ -0,0 +1,83 @@ +import { skipToken } from '@reduxjs/toolkit/query'; +import { useMemo } from 'react'; + +import { t } from '@grafana/i18n'; +import { + GetRepositoryFilesApiResponse, + GetResourceStatsApiResponse, + useGetRepositoryFilesQuery, + useGetResourceStatsQuery, +} from 'app/api/clients/provisioning/v0alpha1'; + +/** + * Calculates resource statistics from API responses + */ +function getResourceStats(files?: GetRepositoryFilesApiResponse, stats?: GetResourceStatsApiResponse) { + const isSupportedFile = (path: string) => path.endsWith('.json') || path.endsWith('.yaml'); + + const items = files?.items ?? []; + + const fileCount = items.filter((file) => { + const path = file.path ?? ''; + return isSupportedFile(path); + }).length; + + let counts: string[] = []; + let resourceCount = 0; + + stats?.instance?.forEach((stat) => { + switch (stat.group) { + case 'folders': + case 'folder.grafana.app': + resourceCount += stat.count; + counts.push(t('provisioning.bootstrap-step.folders-count', '{{count}} folder', { count: stat.count })); + break; + case 'dashboard.grafana.app': + resourceCount += stat.count; + counts.push(t('provisioning.bootstrap-step.dashboards-count', '{{count}} dashboard', { count: stat.count })); + break; + } + }); + + return { + fileCount, + resourceCount, + resourceCountString: counts.join(',\n'), + }; +} + +/** + * Hook that provides resource statistics and sync logic + */ +export function useResourceStats(repoName?: string, isLegacyStorage?: boolean) { + const resourceStatsQuery = useGetResourceStatsQuery(repoName ? undefined : skipToken); + const filesQuery = useGetRepositoryFilesQuery(repoName ? { name: repoName } : skipToken); + + const isLoading = resourceStatsQuery.isLoading || filesQuery.isLoading; + + const { resourceCount, resourceCountString, fileCount } = useMemo( + () => getResourceStats(filesQuery.data, resourceStatsQuery.data), + [filesQuery.data, resourceStatsQuery.data] + ); + + const requiresMigration = isLegacyStorage || resourceCount > 0; + const shouldSkipSync = !requiresMigration && resourceCount === 0 && fileCount === 0; + + // Format display strings + const resourceCountDisplay = + resourceCount > 0 ? resourceCountString : t('provisioning.bootstrap-step.empty', 'Empty'); + const fileCountDisplay = + fileCount > 0 + ? t('provisioning.bootstrap-step.files-count', '{{count}} files', { count: fileCount }) + : t('provisioning.bootstrap-step.empty', 'Empty'); + + return { + resourceCount, + resourceCountString: resourceCountDisplay, + fileCount, + fileCountString: fileCountDisplay, + isLoading, + requiresMigration, + shouldSkipSync, + }; +} diff --git a/public/app/features/provisioning/Wizard/types.ts b/public/app/features/provisioning/Wizard/types.ts index f1d3d404075..18c9fe36149 100644 --- a/public/app/features/provisioning/Wizard/types.ts +++ b/public/app/features/provisioning/Wizard/types.ts @@ -1,6 +1,6 @@ import { RepositorySpec, SyncOptions } from 'app/api/clients/provisioning/v0alpha1'; -import { RepositoryFormData } from '../types'; +import { ProvisioningErrorInfo, RepositoryFormData } from '../types'; export type WizardStep = 'connection' | 'bootstrap' | 'finish' | 'synchronize'; @@ -26,12 +26,17 @@ export interface ModeOption { subtitle: string; } -export interface SystemState { - resourceCount: number; - resourceCountString: string; - fileCount: number; - actions: ModeOption[]; -} - export type StepStatus = 'idle' | 'running' | 'error' | 'success'; -export type StepStatusInfo = { status: StepStatus } | { status: 'error'; error: string }; + +export const RepoTypeDisplay: { [key in RepoType]: string } = { + github: 'GitHub', + gitlab: 'GitLab', + bitbucket: 'Bitbucket', + git: 'Git', + local: 'Local', +}; + +export type StepStatusInfo = + | { status: 'idle' | 'running' | 'success' } + | { status: 'error'; error: string | ProvisioningErrorInfo } + | { status: 'warning'; warning: string | ProvisioningErrorInfo }; diff --git a/public/app/features/provisioning/constants.ts b/public/app/features/provisioning/constants.ts index 74beb4a06f9..9c7e9ae4713 100644 --- a/public/app/features/provisioning/constants.ts +++ b/public/app/features/provisioning/constants.ts @@ -1,3 +1,5 @@ export const PROVISIONING_URL = '/admin/provisioning'; export const CONNECT_URL = `${PROVISIONING_URL}/connect`; export const GETTING_STARTED_URL = `${PROVISIONING_URL}/getting-started`; + +export const DEFAULT_REPOSITORY_TYPES: Array<'github' | 'local'> = ['github', 'local']; diff --git a/public/app/features/provisioning/hooks/usePullRequestParam.ts b/public/app/features/provisioning/hooks/usePullRequestParam.ts index e4432232900..81d10210503 100644 --- a/public/app/features/provisioning/hooks/usePullRequestParam.ts +++ b/public/app/features/provisioning/hooks/usePullRequestParam.ts @@ -5,9 +5,13 @@ export const usePullRequestParam = () => { const [params] = useUrlParams(); const prParam = params.get('pull_request_url'); const newPrParam = params.get('new_pull_request_url'); + const repoUrl = params.get('repo_url'); + const repoType = params.get('repo_type'); return { prURL: prParam ? textUtil.sanitizeUrl(prParam) : undefined, newPrURL: newPrParam ? textUtil.sanitizeUrl(newPrParam) : undefined, + repoURL: repoUrl ? textUtil.sanitizeUrl(repoUrl) : undefined, + repoType: repoType ? textUtil.sanitizeUrl(repoType) : undefined, }; }; diff --git a/public/app/features/provisioning/img/bitbucket.svg b/public/app/features/provisioning/img/bitbucket.svg new file mode 100644 index 00000000000..d809d73b53c --- /dev/null +++ b/public/app/features/provisioning/img/bitbucket.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/public/app/features/provisioning/img/git.svg b/public/app/features/provisioning/img/git.svg new file mode 100644 index 00000000000..a22043d6ef3 --- /dev/null +++ b/public/app/features/provisioning/img/git.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/public/app/features/provisioning/img/gitlab.svg b/public/app/features/provisioning/img/gitlab.svg new file mode 100644 index 00000000000..fac27c2c322 --- /dev/null +++ b/public/app/features/provisioning/img/gitlab.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/public/img/provisioning/provisioning.svg b/public/app/features/provisioning/img/provisioning.svg similarity index 100% rename from public/img/provisioning/provisioning.svg rename to public/app/features/provisioning/img/provisioning.svg diff --git a/public/app/features/provisioning/types.ts b/public/app/features/provisioning/types.ts index c5bdce6b1ab..8f8ee2df385 100644 --- a/public/app/features/provisioning/types.ts +++ b/public/app/features/provisioning/types.ts @@ -1,12 +1,60 @@ -import { GitHubRepositoryConfig, LocalRepositoryConfig, RepositorySpec } from '../../api/clients/provisioning/v0alpha1'; +import { ReactNode } from 'react'; +import { Path, UseFormReturn } from 'react-hook-form'; -export type RepositoryFormData = Omit & +import { SelectableValue } from '@grafana/data'; + +import { + BitbucketRepositoryConfig, + GitHubRepositoryConfig, + GitLabRepositoryConfig, + GitRepositoryConfig, + LocalRepositoryConfig, + RepositorySpec, +} from '../../api/clients/provisioning/v0alpha1'; + +// Repository type definition - extracted from API client +export type RepositoryType = RepositorySpec['type']; + +// Field configuration interface +export interface RepositoryFieldData { + label: string; + type: 'text' | 'secret' | 'switch' | 'select' | 'checkbox' | 'custom' | 'component' | 'number'; + description?: string | ReactNode; + placeholder?: string; + path?: Path; // Optional nested field path, e.g., 'sync.intervalSeconds' + validation?: { + required?: boolean | string; + message?: string; + validate?: (value: unknown) => boolean | string; + }; + defaultValue?: SelectableValue | string | boolean; + options?: Array>; + multi?: boolean; + allowCustomValue?: boolean; + hidden?: boolean; + content?: (setValue: UseFormReturn['setValue']) => ReactNode; // For custom fields +} + +export type RepositoryFormData = Omit & + BitbucketRepositoryConfig & + GitRepositoryConfig & GitHubRepositoryConfig & + GitLabRepositoryConfig & LocalRepositoryConfig & { readOnly: boolean; prWorkflow: boolean; }; +export type RepositorySettingsField = Path; + +// Section configuration +export interface RepositorySection { + name: string; + id: string; + hidden?: boolean; + fields: RepositorySettingsField[]; +} + // Added to DashboardDTO to help editor export interface ProvisioningPreview { repo: string; @@ -38,6 +86,11 @@ export type FileDetails = { export type HistoryListResponse = { apiVersion?: string; kind?: string; - metadata?: any; + metadata?: Record; items?: HistoryItem[]; }; + +export interface ProvisioningErrorInfo { + title?: string; + message?: string | string[]; +} diff --git a/public/app/features/provisioning/utils/data.ts b/public/app/features/provisioning/utils/data.ts index 3f491e1ea16..acecb4d1812 100644 --- a/public/app/features/provisioning/utils/data.ts +++ b/public/app/features/provisioning/utils/data.ts @@ -1,4 +1,4 @@ -import { RepositorySpec } from 'app/api/clients/provisioning/v0alpha1'; +import { Repository, RepositorySpec } from 'app/api/clients/provisioning/v0alpha1'; import { RepositoryFormData } from '../types'; @@ -15,22 +15,52 @@ const getWorkflows = (data: RepositoryFormData): RepositorySpec['workflows'] => return [...workflows, 'branch']; }; -export const dataToSpec = (data: RepositoryFormData): RepositorySpec => { +export const dataToSpec = (data: RepositoryFormData, existingRepository?: Repository): RepositorySpec => { const spec: RepositorySpec = { type: data.type, sync: data.sync, title: data.title || '', workflows: getWorkflows(data), }; + + const baseConfig = { + url: data.url || '', + branch: data.branch, + token: data.token, + path: data.path, + encryptedToken: data.encryptedToken, + }; + switch (data.type) { case 'github': spec.github = { + ...baseConfig, generateDashboardPreviews: data.generateDashboardPreviews, - url: data.url || '', - branch: data.branch, - token: data.token, - path: data.path, }; + // Handle token merging for existing repositories + if (existingRepository?.spec?.github) { + spec.github.token = data.token || existingRepository.spec.github.token; + spec.github.encryptedToken = existingRepository.spec.github.encryptedToken; + } + break; + case 'gitlab': + spec.gitlab = baseConfig; + // Handle token merging for existing repositories + if (existingRepository?.spec?.gitlab) { + spec.gitlab.token = data.token || existingRepository.spec.gitlab.token; + spec.gitlab.encryptedToken = existingRepository.spec.gitlab.encryptedToken; + } + break; + case 'bitbucket': + spec.bitbucket = baseConfig; + // Handle token merging for existing repositories + if (existingRepository?.spec?.bitbucket) { + spec.bitbucket.token = data.token || existingRepository.spec.bitbucket.token; + spec.bitbucket.encryptedToken = existingRepository.spec.bitbucket.encryptedToken; + } + break; + case 'git': + spec.git = baseConfig; break; case 'local': spec.local = { @@ -45,14 +75,37 @@ export const dataToSpec = (data: RepositoryFormData): RepositorySpec => { }; export const specToData = (spec: RepositorySpec): RepositoryFormData => { + const remoteConfig = spec.github || spec.gitlab || spec.bitbucket || spec.git; + return structuredClone({ ...spec, - ...spec.github, + ...remoteConfig, ...spec.local, - branch: spec.github?.branch || '', - url: spec.github?.url || '', + branch: remoteConfig?.branch || '', + url: remoteConfig?.url || '', generateDashboardPreviews: spec.github?.generateDashboardPreviews || false, readOnly: !spec.workflows.length, - prWorkflow: spec.workflows.includes('write'), + prWorkflow: spec.workflows.includes('branch'), }); }; + +export const generateRepositoryTitle = (repository: Pick): string => { + switch (repository.type) { + case 'github': + const name = repository.url ?? 'github'; + return name.replace('https://github.com/', ''); + case 'gitlab': + const gitlabName = repository.url ?? 'gitlab'; + return gitlabName.replace('https://gitlab.com/', ''); + case 'bitbucket': + const bitbucketName = repository.url ?? 'bitbucket'; + return bitbucketName.replace('https://bitbucket.org/', ''); + case 'git': + const gitName = repository.url ?? 'git'; + return gitName.replace(/^https?:\/\/[^\/]+\//, ''); + case 'local': + return repository.path ?? 'local'; + default: + return ''; + } +}; diff --git a/public/app/features/provisioning/utils/getFormErrors.ts b/public/app/features/provisioning/utils/getFormErrors.ts index d960df560cb..9a0c1a840f5 100644 --- a/public/app/features/provisioning/utils/getFormErrors.ts +++ b/public/app/features/provisioning/utils/getFormErrors.ts @@ -13,7 +13,21 @@ export type FormErrorTuple = [RepositoryFormPath | null, { message: string } | n * @returns Tuple with form field path and error message */ export const getFormErrors = (errors: ErrorDetails[]): FormErrorTuple => { - const fieldsToValidate = ['local.path', 'github.branch', 'github.url', 'github.token']; + const fieldsToValidate = [ + 'local.path', + 'github.branch', + 'github.url', + 'github.token', + 'gitlab.branch', + 'gitlab.url', + 'gitlab.token', + 'bitbucket.branch', + 'bitbucket.url', + 'bitbucket.token', + 'git.branch', + 'git.url', + 'git.token', + ]; const fieldMap: Record = { path: 'repository.path', branch: 'repository.branch', diff --git a/public/app/features/provisioning/utils/git.ts b/public/app/features/provisioning/utils/git.ts index eb7e334888a..cfc30bbae0d 100644 --- a/public/app/features/provisioning/utils/git.ts +++ b/public/app/features/provisioning/utils/git.ts @@ -24,3 +24,43 @@ export const getRepoHref = (github?: RepositorySpec['github']) => { } return `${github.url}/tree/${github.branch}`; }; + +export const getRepoHrefForProvider = (spec?: RepositorySpec) => { + if (!spec || !spec.type) { + return undefined; + } + + switch (spec.type) { + case 'github': { + const url = spec.github?.url; + const branch = spec.github?.branch; + if (!url) { + return undefined; + } + return branch ? `${url}/tree/${branch}` : url; + } + + case 'gitlab': { + const url = spec.gitlab?.url; + const branch = spec.gitlab?.branch; + if (!url) { + return undefined; + } + return branch ? `${url}/-/tree/${branch}` : url; + } + case 'bitbucket': { + const url = spec.bitbucket?.url; + const branch = spec.bitbucket?.branch; + if (!url) { + return undefined; + } + return branch ? `${url}/src/${branch}` : url; + } + case 'git': { + // Return a generic URL for pure git repositories + return spec.git?.url; + } + default: + return undefined; + } +}; diff --git a/public/app/features/provisioning/utils/repositoryTypes.ts b/public/app/features/provisioning/utils/repositoryTypes.ts new file mode 100644 index 00000000000..f8848044900 --- /dev/null +++ b/public/app/features/provisioning/utils/repositoryTypes.ts @@ -0,0 +1,80 @@ +import { t } from '@grafana/i18n'; +import { IconName } from '@grafana/ui'; + +import { RepoType } from '../Wizard/types'; +import bitbucketSvg from '../img/bitbucket.svg'; +import gitSvg from '../img/git.svg'; +import gitlabSvg from '../img/gitlab.svg'; + +export interface RepositoryTypeConfig { + type: RepoType; + label: string; + description: string; + icon: IconName; + logo?: string; +} + +export const getRepositoryTypeConfigs = (): RepositoryTypeConfig[] => [ + { + type: 'git', + label: t('provisioning.repository-types.pure-git', 'Pure Git'), + description: t('provisioning.repository-types.pure-git-description', 'Connect to any Git repository'), + icon: 'git' as const, + logo: gitSvg, + }, + { + type: 'github', + label: t('provisioning.repository-types.github', 'GitHub'), + description: t('provisioning.repository-types.github-description', 'Connect to GitHub repositories'), + icon: 'github' as const, + }, + { + type: 'gitlab', + label: t('provisioning.repository-types.gitlab', 'GitLab'), + description: t('provisioning.repository-types.gitlab-description', 'Connect to GitLab repositories'), + icon: 'gitlab' as const, + logo: gitlabSvg, + }, + { + type: 'bitbucket', + label: t('provisioning.repository-types.bitbucket', 'Bitbucket'), + description: t('provisioning.repository-types.bitbucket-description', 'Connect to Bitbucket repositories'), + icon: 'bitbucket' as const, + logo: bitbucketSvg, + }, + { + type: 'local', + label: t('provisioning.repository-types.local', 'Local'), + description: t('provisioning.repository-types.local-description', 'Configure file provisioning'), + icon: 'file-alt' as const, + }, +]; + +export const getRepositoryTypeConfig = (type: RepoType): RepositoryTypeConfig | undefined => { + return getRepositoryTypeConfigs().find((config) => config.type === type); +}; + +const GIT_PROVIDER_TYPES = ['github', 'gitlab', 'bitbucket', 'git']; + +export const isGitProvider = (type: RepoType) => { + return GIT_PROVIDER_TYPES.includes(type); +}; + +/** + * Get repository configurations ordered by provider type priority: + * 1. Git providers first (github, gitlab, bitbucket) - excludes pure git + * 2. Other providers (pure git, local) + */ +export const getOrderedRepositoryConfigs = (availableTypes: RepoType[]) => { + const repositoryConfigs = getRepositoryTypeConfigs().filter((config) => availableTypes.includes(config.type)); + + // Separate git providers from other providers + const gitProviders = repositoryConfigs.filter((config) => isGitProvider(config.type) && config.type !== 'git'); + const otherProviders = repositoryConfigs.filter((config) => !isGitProvider(config.type) || config.type === 'git'); + + return { + gitProviders, + otherProviders, + orderedConfigs: [...gitProviders, ...otherProviders], + }; +}; diff --git a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx index d437144b415..7f0c722188a 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -15,7 +15,7 @@ import { import { ConvertFieldTypeOptions, ConvertFieldTypeTransformerOptions } from '@grafana/data/internal'; import { t, Trans } from '@grafana/i18n'; import { Button, InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; -import { allFieldTypeIconOptions, FieldNamePicker } from '@grafana/ui/internal'; +import { getAllFieldTypeIconOptions, FieldNamePicker } from '@grafana/ui/internal'; import { findField } from 'app/features/dimensions/utils'; import { getTransformationContent } from '../docs/getTransformationContent'; @@ -34,7 +34,7 @@ export const ConvertFieldTypeTransformerEditor = ({ options, onChange, }: TransformerUIProps) => { - const allTypes = allFieldTypeIconOptions.filter((v) => v.value !== FieldType.trace); + const allTypes = getAllFieldTypeIconOptions().filter((v) => v.value !== FieldType.trace); const timeZoneOptions: Array> = getTimezoneOptions(true); // Format timezone options diff --git a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx index f4d1773b8f2..1d458d988bd 100644 --- a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx @@ -127,6 +127,7 @@ export const extractFieldsTransformerEditor = ({ interactive={true} tooltip={t('transformers.extract-fields-transformer-editor.tooltip-regexp', 'Example: {{regexExample}}', { regexExample: '/(?.*)/', + interpolation: { escapeValue: false }, })} > = (props) => { ); return ( - + {schema && tables.length === 0 && ( ({ })); describe('Azure Monitor QueryEditor', () => { + beforeEach(() => { + config.featureToggles = {}; + }); + + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + it('renders the Metrics query editor when the query type is Metrics', async () => { const mockDatasource = createMockDatasource(); const mockQuery = { @@ -67,6 +76,65 @@ describe('Azure Monitor QueryEditor', () => { ); }); + it('renders the Logs code editor when there is an existing query and the builder is enabled', async () => { + config.featureToggles.azureMonitorLogsBuilderEditor = true; + const mockDatasource = createMockDatasource(); + const mockQuery = { + ...createMockQuery(), + queryType: AzureQueryType.LogAnalytics, + }; + + render( {}} onRunQuery={() => {}} />); + await waitFor(() => { + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryEditor.container.input) + ).toBeInTheDocument(); + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryBuilder.container.input) + ).not.toBeInTheDocument(); + }); + }); + + it('renders the Logs code editor when there is no existing query and the builder is disabled', async () => { + config.featureToggles.azureMonitorLogsBuilderEditor = false; + const mockDatasource = createMockDatasource(); + const mockQuery = { + ...createMockQuery(), + queryType: AzureQueryType.LogAnalytics, + }; + delete mockQuery.azureLogAnalytics?.query; + + render( {}} onRunQuery={() => {}} />); + await waitFor(() => { + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryEditor.container.input) + ).toBeInTheDocument(); + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryBuilder.container.input) + ).not.toBeInTheDocument(); + }); + }); + + it('renders the Logs builder when there is no existing query and the builder is enabled', async () => { + config.featureToggles.azureMonitorLogsBuilderEditor = true; + const mockDatasource = createMockDatasource(); + const mockQuery = { + ...createMockQuery(), + queryType: AzureQueryType.LogAnalytics, + }; + delete mockQuery.azureLogAnalytics?.query; + + render( {}} onRunQuery={() => {}} />); + await waitFor(() => { + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryEditor.container.input) + ).toBeInTheDocument(); + expect( + screen.queryByTestId(selectors.components.queryEditor.logsQueryBuilder.container.input) + ).not.toBeInTheDocument(); + }); + }); + it('renders the ARG query editor when the query type is ARG', async () => { const mockDatasource = createMockDatasource(); const mockQuery = { diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx index 035a918fc8d..75d89a8fc47 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryHeader.tsx @@ -64,7 +64,13 @@ export const QueryHeader = ({ ...query, azureLogAnalytics: { ...query.azureLogAnalytics, - mode: LogsEditorMode.Builder, + // Builder mode is default unless there is an existing Log Analytics query + // that was not created with the builder + mode: + (query.azureLogAnalytics?.builderQuery === undefined && query.azureLogAnalytics?.query !== undefined) || + !config.featureToggles.azureMonitorLogsBuilderEditor + ? LogsEditorMode.Raw + : LogsEditorMode.Builder, dashboardTime: true, }, }; diff --git a/public/app/plugins/datasource/azuremonitor/e2e/selectors.ts b/public/app/plugins/datasource/azuremonitor/e2e/selectors.ts index 636d8dcac31..6f0b5ccedd0 100644 --- a/public/app/plugins/datasource/azuremonitor/e2e/selectors.ts +++ b/public/app/plugins/datasource/azuremonitor/e2e/selectors.ts @@ -80,6 +80,9 @@ export const components = { button: 'data-testid run-query', }, }, + logsQueryBuilder: { + container: { input: 'data-testid azure-monitor-logs-query-builder' }, + }, argsQueryEditor: { container: { input: 'data-testid azure-monitor-arg-query-editor', diff --git a/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json index 7b5adf4401a..70586891c3d 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json @@ -233,7 +233,7 @@ "title-error-occurred": "Bei der Anforderung von Ressourcen von Azure Monitor ist ein Fehler aufgetreten" }, "scope-selector": { - "label": "" + "label": "Bereich" }, "search": { "aria-label-resource-search": "Ressourcensuche", diff --git a/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json index 4ccef7bbbfe..99def013e2c 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json @@ -233,7 +233,7 @@ "title-error-occurred": "Hiba történt az erőforrások Azure Monitor szolgáltatásból történő lekérése során" }, "scope-selector": { - "label": "" + "label": "Hatókör" }, "search": { "aria-label-resource-search": "Erőforrás keresése", diff --git a/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json index 3ab6b7e9853..5de934ad671 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json @@ -233,7 +233,7 @@ "title-error-occurred": "Terjadi kesalahan saat meminta sumber daya dari Azure Monitor" }, "scope-selector": { - "label": "" + "label": "Ruang Lingkup" }, "search": { "aria-label-resource-search": "Pencarian sumber daya", diff --git a/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json index 165a50a63bc..c876ebe62f2 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json @@ -233,7 +233,7 @@ "title-error-occurred": "Azure Monitorからリソースをリクエスト中にエラーが発生しました" }, "scope-selector": { - "label": "" + "label": "スコープ" }, "search": { "aria-label-resource-search": "リソース検索", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 7fa35b8ad0e..e0828ae1816 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/grafana-azure-monitor-datasource", "description": "Grafana data source for Azure Monitor", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/i18n": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/i18n": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", "i18next": "^25.0.0", @@ -26,15 +26,15 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index e844a41d5c9..c27670248a3 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/stackdriver", "description": "Grafana data source for Google Cloud Monitoring", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", "@grafana/google-sdk": "0.3.4", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", "i18next": "^25.0.0", @@ -26,16 +26,16 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/debounce-promise": "3.1.9", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/dashboard/datasource.test.ts b/public/app/plugins/datasource/dashboard/datasource.test.ts index 26413c094df..3bb2fbc6b3c 100644 --- a/public/app/plugins/datasource/dashboard/datasource.test.ts +++ b/public/app/plugins/datasource/dashboard/datasource.test.ts @@ -7,15 +7,19 @@ import { getDefaultTimeRange, LoadingState, standardTransformersRegistry, + FieldType, + DataFrame, + AdHocVariableFilter, } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; -import { setPluginImportUtils } from '@grafana/runtime'; +import { setPluginImportUtils, config } from '@grafana/runtime'; import { SafeSerializableSceneObject, SceneDataNode, SceneDataTransformer, SceneFlexItem, SceneFlexLayout, + SceneObject, VizPanel, } from '@grafana/scenes'; import { getVizPanelKeyForPanelId } from 'app/features/dashboard-scene/utils/utils'; @@ -131,6 +135,441 @@ describe('DashboardDatasource', () => { // on further emissions the result should be the unmodified original dataframe expect(rsp!.data[0].fields[0].state).toEqual({}); }); + + describe('AdHoc Filtering', () => { + // Shared test utilities + interface TestField { + name: string; + type: FieldType; + values: unknown[]; + } + + function createTestFrame(fields: TestField[]) { + return { + name: 'TestData', + fields: fields.map((field) => ({ + name: field.name, + type: field.type, + values: field.values, + config: {}, + state: {}, + })), + length: fields[0]?.values.length || 0, + refId: 'A', + }; + } + + function createQueryRequest(filters: AdHocVariableFilter[], scene: SceneObject) { + return { + timezone: 'utc', + targets: [{ refId: 'A', panelId: 1 }], + requestId: '', + interval: '', + intervalMs: 0, + range: getDefaultTimeRange(), + scopedVars: { + __sceneObject: new SafeSerializableSceneObject(scene), + }, + app: '', + startTime: 0, + filters: filters, + }; + } + + // Test AdHoc filtering via the Public API first, to ensure Integration + describe('Integration (Public API)', () => { + const originalToggleValue = config.featureToggles.dashboardDsAdHocFiltering; + + beforeEach(() => { + config.featureToggles.dashboardDsAdHocFiltering = true; + }); + + afterEach(() => { + config.featureToggles.dashboardDsAdHocFiltering = originalToggleValue; + }); + + it('should apply basic filtering end-to-end through public query method', async () => { + const testFrame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: new VizPanel({ + key: getVizPanelKeyForPanelId(1), + $data: new SceneDataNode({ + data: { + series: [testFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }, + }), + }), + }), + ], + }); + + const ds = new DashboardDatasource({} as DataSourceInstanceSettings); + const filters: AdHocVariableFilter[] = [{ key: 'name', operator: '=', value: 'John' }]; + + const observable = ds.query(createQueryRequest(filters, scene)); + + let result: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (result = data) }); + + expect(result?.data[0].fields[0].values).toEqual(['John']); + expect(result?.data[0].fields[1].values).toEqual([25]); + expect(result?.data[0].length).toBe(1); + }); + + it('should respect feature toggle and not filter when disabled', async () => { + // Temporarily disable the feature toggle for this test + config.featureToggles.dashboardDsAdHocFiltering = false; + + const testFrame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: new VizPanel({ + key: getVizPanelKeyForPanelId(1), + $data: new SceneDataNode({ + data: { + series: [testFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }, + }), + }), + }), + ], + }); + + const ds = new DashboardDatasource({} as DataSourceInstanceSettings); + const filters: AdHocVariableFilter[] = [{ key: 'name', operator: '=', value: 'John' }]; + + const observable = ds.query(createQueryRequest(filters, scene)); + + let result: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (result = data) }); + + // Should return unfiltered data since feature toggle is disabled + expect(result?.data[0].fields[0].values).toEqual(['John', 'Jane', 'Bob']); + expect(result?.data[0].fields[1].values).toEqual([25, 30, 35]); + expect(result?.data[0].length).toBe(3); + }); + + it('should apply multiple filters with AND logic through public API', async () => { + const testFrame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'status', type: FieldType.string, values: ['active', 'active', 'inactive'] }, + ]); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: new VizPanel({ + key: getVizPanelKeyForPanelId(1), + $data: new SceneDataNode({ + data: { + series: [testFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }, + }), + }), + }), + ], + }); + + const ds = new DashboardDatasource({} as DataSourceInstanceSettings); + const filters: AdHocVariableFilter[] = [ + { key: 'name', operator: '!=', value: 'John' }, + { key: 'status', operator: '=', value: 'active' }, + ]; + + const observable = ds.query(createQueryRequest(filters, scene)); + + let result: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (result = data) }); + + expect(result?.data[0].fields[0].values).toEqual(['Jane']); + expect(result?.data[0].fields[1].values).toEqual(['active']); + expect(result?.data[0].length).toBe(1); + }); + }); + + // Now test AdHoc filtering with unit-tests to test all aspects of behaviour. + // Here we test a private method, which allows for smaller, simpler, + // faster tests. + // This is the bottom of the 'Test Pyramid' + describe('Algorithm (but by testing a Private Method)', () => { + const ds = new DashboardDatasource({} as DataSourceInstanceSettings); + + // Type-safe interface for accessing private methods in tests + interface DashboardDatasourceWithPrivateMethods { + applyAdHocFilters: (frame: DataFrame, filters: AdHocVariableFilter[]) => DataFrame; + } + + it('should apply equality filter correctly', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '=', value: 'John' }, + ]); + + expect(result.fields[0].values).toEqual(['John']); + expect(result.fields[1].values).toEqual([25]); + expect(result.length).toBe(1); + }); + + it('should apply not-equal filter correctly', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '!=', value: 'John' }, + ]); + + expect(result.fields[0].values).toEqual(['Jane', 'Bob']); + expect(result.fields[1].values).toEqual([30, 35]); + expect(result.length).toBe(2); + }); + + it('should apply multiple filters with AND logic', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'status', type: FieldType.string, values: ['active', 'active', 'inactive'] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '!=', value: 'John' }, + { key: 'status', operator: '=', value: 'active' }, + ]); + + expect(result.fields[0].values).toEqual(['Jane']); + expect(result.fields[1].values).toEqual(['active']); + expect(result.length).toBe(1); + }); + + it('should handle null values correctly', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', null, 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '!=', value: 'John' }, + ]); + + expect(result.fields[0].values).toEqual([null, 'Bob']); + expect(result.fields[1].values).toEqual([30, 35]); + expect(result.length).toBe(2); + }); + + it('should apply equality filter on numeric fields', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'age', operator: '=', value: '30' }, + ]); + + expect(result.fields[0].values).toEqual(['Jane']); + expect(result.fields[1].values).toEqual([30]); + expect(result.length).toBe(1); + }); + + it('should apply not-equal filter on numeric fields', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'age', operator: '!=', value: '30' }, + ]); + + expect(result.fields[0].values).toEqual(['John', 'Bob']); + expect(result.fields[1].values).toEqual([25, 35]); + expect(result.length).toBe(2); + }); + + it('should handle numeric fields with null values', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, null, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'age', operator: '!=', value: '25' }, + ]); + + expect(result.fields[0].values).toEqual(['Jane', 'Bob']); + expect(result.fields[1].values).toEqual([null, 35]); + expect(result.length).toBe(2); + }); + + it('should handle mixed string and numeric filtering', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob', 'Alice'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 25, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '!=', value: 'Bob' }, + { key: 'age', operator: '=', value: '25' }, + ]); + + // Should match: name != 'Bob' AND age = 25 + // John: !Bob + 25 ✓ + // Jane: !Bob + 30 ✗ + // Bob: Bob + 25 ✗ + // Alice: !Bob + 35 ✗ + expect(result.fields[0].values).toEqual(['John']); + expect(result.fields[1].values).toEqual([25]); + expect(result.length).toBe(1); + }); + + it('should handle floating point numbers correctly', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['Alice', 'Bob', 'Charlie', 'Diana'] }, + { name: 'score', type: FieldType.number, values: [95.5, 87.25, 95.5, 92.0] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'score', operator: '=', value: '95.5' }, + ]); + + expect(result.fields[0].values).toEqual(['Alice', 'Charlie']); + expect(result.fields[1].values).toEqual([95.5, 95.5]); + expect(result.length).toBe(2); + }); + + it('should handle floating point numbers with != operator', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['Alice', 'Bob', 'Charlie'] }, + { name: 'temperature', type: FieldType.number, values: [98.6, 99.1, 98.6] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'temperature', operator: '!=', value: '98.6' }, + ]); + + expect(result.fields[0].values).toEqual(['Bob']); + expect(result.fields[1].values).toEqual([99.1]); + expect(result.length).toBe(1); + }); + + it('should handle precision edge cases with floats', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['Test1', 'Test2', 'Test3'] }, + { name: 'value', type: FieldType.number, values: [0.1 + 0.2, 0.3, 1.0000001] }, + ]); + + // Note: 0.1 + 0.2 !== 0.3 in JavaScript due to floating point precision + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'value', operator: '=', value: '0.3' }, + ]); + + // Should only match the exact 0.3, not the computed 0.1 + 0.2 + expect(result.fields[0].values).toEqual(['Test2']); + expect(result.fields[1].values).toEqual([0.3]); + expect(result.length).toBe(1); + }); + + it('should handle empty data frames', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: [] }, + { name: 'age', type: FieldType.number, values: [] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'name', operator: '=', value: 'John' }, + ]); + + expect(result.fields[0].values).toEqual([]); + expect(result.fields[1].values).toEqual([]); + expect(result.length).toBe(0); + }); + + it.skip('should handle remaining operators', () => { + // Not yet implemented, so we explicitly don't specify any behaviour for this + }); + + it.skip('should handle remaining field types (eg. date)', () => { + // Not yet implemented, so we explicitly don't specify any behaviour for this + }); + + it('should handle filters on missing fields with = operator', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'missing_field', operator: '=', value: 'test' }, + ]); + + // Should return empty result since field doesn't exist + expect(result.fields[0].values).toEqual([]); + expect(result.fields[1].values).toEqual([]); + expect(result.length).toBe(0); + }); + + it('should handle filters on missing fields with != operator', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'missing_field', operator: '!=', value: 'test' }, + ]); + + // Should return all rows since field doesn't exist (all rows are "not equal") + expect(result.fields[0].values).toEqual(['John', 'Jane', 'Bob']); + expect(result.fields[1].values).toEqual([25, 30, 35]); + expect(result.length).toBe(3); + }); + + it('should handle complex filtering scenario', () => { + const frame = createTestFrame([ + { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Admin', 'Bob'] }, + { name: 'status', type: FieldType.string, values: ['active', 'inactive', 'active', 'active'] }, + { name: 'age', type: FieldType.number, values: [25, 30, 35, 40] }, + ]); + + const result = (ds as unknown as DashboardDatasourceWithPrivateMethods).applyAdHocFilters(frame, [ + { key: 'status', operator: '=', value: 'active' }, + { key: 'name', operator: '!=', value: 'Admin' }, + { key: 'missing_field', operator: '!=', value: 'ignored' }, // Should be ignored + ]); + + // Should match: status=active AND name!=Admin + // John: active + !Admin ✓ + // Jane: inactive + !Admin ✗ + // Admin: active + Admin ✗ + // Bob: active + !Admin ✓ + expect(result.fields[0].values).toEqual(['John', 'Bob']); + expect(result.fields[1].values).toEqual(['active', 'active']); + expect(result.fields[2].values).toEqual([25, 40]); + expect(result.length).toBe(2); + }); + }); + }); }); function setup(query: DashboardQuery, requestId?: string) { @@ -140,7 +579,6 @@ function setup(query: DashboardQuery, requestId?: string) { series: [arrayToDataFrame([1, 2, 3])], state: LoadingState.Done, timeRange: getDefaultTimeRange(), - structureRev: 11, }, }), transformations: [{ id: 'reduce', options: {} }], diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index a9e34586d4f..e98e140eb0a 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -12,7 +12,13 @@ import { DataFrame, LoadingState, Field, + FieldType, + AdHocVariableFilter, + MetricFindValue, + getValueMatcher, + ValueMatcherID, } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes'; import { activateSceneObjectAndParentTree, @@ -71,6 +77,9 @@ export class DashboardDatasource extends DataSourceApi { return of({ data: [] }); } + // Extract AdHoc filters from the request + const adHocFilters = options.filters || []; + return defer(() => { if (!sourceDataProvider!.isActive && sourceDataProvider?.setContainerWidth) { sourceDataProvider?.setContainerWidth(500); @@ -82,7 +91,7 @@ export class DashboardDatasource extends DataSourceApi { debounceTime(50), map((result) => { return { - data: this.getDataFramesForQueryTopic(result.data, query), + data: this.getDataFramesForQueryTopic(result.data, query, adHocFilters), state: result.data.state, errors: result.data.errors, error: result.data.error, @@ -95,7 +104,11 @@ export class DashboardDatasource extends DataSourceApi { }); } - private getDataFramesForQueryTopic(data: PanelData, query: DashboardQuery): DataFrame[] { + private getDataFramesForQueryTopic( + data: PanelData, + query: DashboardQuery, + filters: AdHocVariableFilter[] + ): DataFrame[] { const annotations = data.annotations ?? []; if (query.topic === DataTopic.Annotations) { return annotations.map((frame) => ({ @@ -111,6 +124,13 @@ export class DashboardDatasource extends DataSourceApi { ...s, fields: s.fields.map((field: Field) => ({ ...field, + config: { + ...field.config, + // Enable AdHoc filtering for string and numeric fields only when feature toggle is enabled + filterable: config.featureToggles.dashboardDsAdHocFiltering + ? field.type === FieldType.string || field.type === FieldType.number + : field.config.filterable, + }, state: { ...field.state, }, @@ -118,10 +138,149 @@ export class DashboardDatasource extends DataSourceApi { }; }); - return [...series, ...annotations]; + if (!config.featureToggles.dashboardDsAdHocFiltering || filters.length === 0) { + return [...series, ...annotations]; + } + + // Apply AdHoc filters to series data + const filteredSeries = series.map((frame) => this.applyAdHocFilters(frame, filters)); + return [...filteredSeries, ...annotations]; } } + /** + * Apply AdHoc filters to a DataFrame + * Optimized version with pre-computed field indices and value matchers for better performance + */ + private applyAdHocFilters(frame: DataFrame, filters: AdHocVariableFilter[]): DataFrame { + if (filters.length === 0 || frame.length === 0) { + return frame; + } + + // Pre-compute field indices and value matchers for better performance + const filterFieldIndices = filters + .map((filter) => { + const fieldIndex = frame.fields.findIndex((f) => f.name === filter.key); + return { filter, fieldIndex, matcher: this.createValueMatcher(filter, fieldIndex, frame) }; + }) + .filter(({ filter, fieldIndex, matcher }) => { + // If field is not present: + // - Keep filters with '=' operator (will always be false - reject rows) + // - Remove filters with '!=' operator (will always be true - no effect) + if (fieldIndex === -1) { + return filter.operator === '='; + } + // Only keep filters with valid matchers + return matcher !== null; + }); + + // If no filters remain after optimization, return original frame + if (filterFieldIndices.length === 0) { + return frame; + } + + // Short-circuit: if any filter has '=' operator with missing field, reject all rows + const hasImpossibleFilter = filterFieldIndices.some(({ fieldIndex }) => fieldIndex === -1); + if (hasImpossibleFilter) { + return this.reconstructDataFrame(frame); + } + + const matchingRows = new Set(); + + // Check each row to see if it matches all filters (AND logic) + for (let rowIndex = 0; rowIndex < frame.length; rowIndex++) { + const rowMatches = filterFieldIndices.every(({ matcher, fieldIndex }) => { + const field = frame.fields[fieldIndex]; + + // Use Grafana's value matcher system + return matcher?.(rowIndex, field, frame, [frame]) ?? false; + }); + + if (rowMatches) { + matchingRows.add(rowIndex); + } + } + + // Early return if no filtering occurred + if (matchingRows.size === frame.length) { + return frame; + } + + return this.reconstructDataFrame(frame, matchingRows); + } + + /** + * Create a value matcher from an AdHoc filter + */ + private createValueMatcher(filter: AdHocVariableFilter, fieldIndex: number, frame: DataFrame) { + // Return null for missing fields - they are handled separately + if (fieldIndex === -1) { + return null; + } + + const field = frame.fields[fieldIndex]; + + // Only support string and numeric fields when feature toggle is enabled + if (config.featureToggles.dashboardDsAdHocFiltering) { + if (field.type !== FieldType.string && field.type !== FieldType.number) { + return null; + } + } + + // Map operator to matcher ID + let matcherId: ValueMatcherID; + switch (filter.operator) { + case '=': + matcherId = ValueMatcherID.equal; + break; + case '!=': + matcherId = ValueMatcherID.notEqual; + break; + default: + return null; // Unknown operator + } + + try { + return getValueMatcher({ + id: matcherId, + options: { value: filter.value }, + }); + } catch (error) { + console.warn('Failed to create value matcher for filter:', filter, error); + return null; + } + } + + /** + * Reconstruct DataFrame with only matching rows + * Optimized to avoid repeated array operations + */ + private reconstructDataFrame(frame: DataFrame, matchingRows?: Set): DataFrame { + // Default to empty set if no matching rows provided (reject all rows) + const rows = matchingRows ?? new Set(); + + const fields: Field[] = frame.fields.map((field) => { + // Pre-allocate array and use direct assignment for better performance with large datasets + const newValues = new Array(rows.size); + let i = 0; + for (const rowIndex of rows) { + newValues[i++] = field.values[rowIndex]; + } + + return { + ...field, + values: newValues, + state: {}, // Clean the state as it's being recalculated + }; + }); + + return { + ...frame, + fields: fields, + length: rows.size, + }; + } + private findSourcePanel(scene: SceneObject, panelId: number) { // We're trying to find the original panel, not a cloned one, since `panelId` alone cannot resolve clones return findOriginalVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId)); @@ -169,4 +328,10 @@ export class DashboardDatasource extends DataSourceApi { testDatasource(): Promise { return Promise.resolve({ message: '', status: '' }); } + + getTagKeys(): Promise { + // Stub implementation to indicate AdHoc filter support + // Full implementation will be added in future PRs + return Promise.resolve([]); + } } diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index f3f468299f2..07af56ca8d6 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -2,28 +2,28 @@ "name": "@grafana-plugins/grafana-postgresql-datasource", "description": "PostgreSQL data source plugin", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/sql": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/sql": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.2", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 4abe65b782a..595dbef2981 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-pyroscope-datasource", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "fast-deep-equal": "^3.1.3", "lodash": "4.17.21", "monaco-editor": "0.34.1", @@ -20,14 +20,14 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index b7e125ef6a9..5118426ef3b 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-testdata-datasource", "description": "Generates test data in different forms", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -21,16 +21,16 @@ "uuid": "11.1.0" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 294ea993a10..34998789a2f 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/jaeger", "description": "Jaeger plugin for Grafana", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", @@ -26,12 +26,12 @@ "@grafana/plugin-configs": "workspace:*", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-window": "1.8.8", diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index f621c70cc40..096eedc45f7 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -2,16 +2,16 @@ "name": "@grafana-plugins/loki", "description": "Loki data source plugin for Grafana", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", "@grafana/lezer-logql": "0.2.8", "@grafana/llm": "0.22.1", "@grafana/monaco-logql": "^0.0.8", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -24,16 +24,16 @@ "uuid": "11.1.0" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts index 04cb8c15055..12bb8b04de0 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts @@ -6,7 +6,7 @@ import { LokiDatasource } from './datasource'; import { createLokiDatasource } from './mocks/datasource'; import { getMockFrames } from './mocks/frames'; import { runShardSplitQuery } from './shardQuerySplitting'; -import { LokiQuery, LokiQueryDirection } from './types'; +import { LokiQuery, LokiQueryDirection, LokiQueryType } from './types'; jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('uuid'), @@ -100,6 +100,26 @@ describe('runShardSplitQuery()', () => { }); }); + test('Runs multiple non-sharded queries', async () => { + const request = createRequest([ + { expr: 'count_over_time({a="b"}[$__auto])', refId: 'A' }, + { expr: 'count_over_time({a="b"}[$__auto])', refId: 'B', queryType: LokiQueryType.Instant }, + ]); + datasource = createLokiDatasource({ + replace: (input = '') => { + return input.replace('$__auto', '5m').replace('$step', '5m'); + }, + getVariables: () => [], + }); + jest.spyOn(datasource, 'runQuery').mockReturnValue(of({ data: [] })); + datasource.languageProvider.fetchLabelValues = jest.fn(); + jest.mocked(datasource.languageProvider.fetchLabelValues).mockResolvedValue([]); + await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { + // 5 shards, 3 groups + empty shard group, 4 requests + expect(datasource.runQuery).toHaveBeenCalledTimes(2); + }); + }); + test('Users query splitting for querying over a day', async () => { await expect(runShardSplitQuery(datasource, request)).toEmitValuesWith(() => { // 5 shards, 3 groups + empty shard group, 4 requests diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.ts index 6a949ece553..9cd5207da6d 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.ts @@ -87,8 +87,9 @@ function splitQueriesByStreamShard( } const nextRequest = () => { + // Find the next group to execute, which can be queries with pending shards to execute, or the next query with no shards. const nextGroup = - groups[group + 1] && groupHasPendingRequests(groups[group + 1]) + groups[group + 1] && (groups[group + 1].shards === undefined || groupHasPendingRequests(groups[group + 1])) ? groups[group + 1] : groups.find((shardGroup) => groupHasPendingRequests(shardGroup)); diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 7b8f678c101..f6701f152b0 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -2,29 +2,29 @@ "name": "@grafana-plugins/mssql", "description": "MSSQL data source plugin", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/i18n": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/i18n": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/sql": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/sql": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.2", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "i18next-parser": "9.3.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index f9db0ce2f18..a54da9e6b23 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -2,28 +2,28 @@ "name": "@grafana-plugins/mysql", "description": "MySQL data source plugin", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", "@grafana/plugin-ui": "0.10.7", - "@grafana/runtime": "12.1.0-pre", - "@grafana/sql": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/sql": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.2", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.1.0-pre", - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/e2e-selectors": "12.2.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index 360d9bbc5fb..d411208a322 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/parca", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.1.0-pre", - "@grafana/runtime": "12.1.0-pre", - "@grafana/schema": "12.1.0-pre", - "@grafana/ui": "12.1.0-pre", + "@grafana/data": "12.2.0-pre", + "@grafana/runtime": "12.2.0-pre", + "@grafana/schema": "12.2.0-pre", + "@grafana/ui": "12.2.0-pre", "lodash": "4.17.21", "monaco-editor": "0.34.1", "react": "18.3.1", @@ -18,12 +18,12 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/public/app/plugins/datasource/tempo/QueryField.tsx b/public/app/plugins/datasource/tempo/QueryField.tsx index d64fcba919e..c0a47c8f79c 100644 --- a/public/app/plugins/datasource/tempo/QueryField.tsx +++ b/public/app/plugins/datasource/tempo/QueryField.tsx @@ -161,6 +161,7 @@ class TempoQueryFieldComponent extends PureComponent { app={app} onClearResults={this.onClearResults} addVariablesToOptions={this.props.addVariablesToOptions} + range={this.props.range} /> )} {query.queryType === 'serviceMap' && ( @@ -174,6 +175,7 @@ class TempoQueryFieldComponent extends PureComponent { onChange={onChange} app={app} onClearResults={this.onClearResults} + range={this.props.range} /> )} diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx index c7b7bb6832c..8478963bed0 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx @@ -1,9 +1,9 @@ import { css } from '@emotion/css'; import { uniq } from 'lodash'; -import { useState, useMemo } from 'react'; +import { useMemo, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, TimeRange } from '@grafana/data'; import { TemporaryAlert } from '@grafana/o11y-ds-frontend'; import { FetchError, getTemplateSrv, isFetchError } from '@grafana/runtime'; import { Select, Stack, useStyles2, InputActionMeta } from '@grafana/ui'; @@ -30,6 +30,8 @@ interface Props { isMulti?: boolean; allowCustomValue?: boolean; addVariablesToOptions?: boolean; + range?: TimeRange; + timeRangeForTags?: number; } const SearchField = ({ filter, @@ -45,6 +47,8 @@ const SearchField = ({ addVariablesToOptions, isMulti = true, allowCustomValue = true, + range, + timeRangeForTags, }: Props) => { const styles = useStyles2(getStyles); const [alertText, setAlertText] = useState(); @@ -57,7 +61,14 @@ const SearchField = ({ const updateOptions = async () => { try { - const result = filter.tag ? await datasource.languageProvider.getOptionsV2(scopedTag, query) : []; + const result = filter.tag + ? await datasource.languageProvider.getOptionsV2({ + tag: scopedTag, + query, + timeRangeForTags, + range, + }) + : []; setAlertText(undefined); setError(null); return result; @@ -77,6 +88,8 @@ const SearchField = ({ datasource.languageProvider, setError, query, + range, + timeRangeForTags, ]); // Add selected option if it doesn't exist in the current list of options diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx index d9ecd049504..a8266bfde8c 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { useCallback, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, TimeRange } from '@grafana/data'; import { AccessoryButton } from '@grafana/plugin-ui'; import { FetchError } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; @@ -41,6 +41,8 @@ interface Props { hideValues?: boolean; requireTagAndValue?: boolean; addVariablesToOptions?: boolean; + range?: TimeRange; + timeRangeForTags?: number; } const TagsInput = ({ updateFilter, @@ -54,6 +56,8 @@ const TagsInput = ({ requireTagAndValue, generateQueryWithoutFilter, addVariablesToOptions, + range, + timeRangeForTags, }: Props) => { const styles = useStyles2(getStyles); const handleOnAdd = useCallback( @@ -91,6 +95,8 @@ const TagsInput = ({ hideValue={hideValues} query={generateQueryWithoutFilter(f)} addVariablesToOptions={addVariablesToOptions} + range={range} + timeRangeForTags={timeRangeForTags} /> {(validInput(f) || filters.length > 1) && ( void; app?: CoreApp; addVariablesToOptions?: boolean; + range?: TimeRange; } const hardCodedFilterIds = ['min-duration', 'max-duration', 'status']; -const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVariablesToOptions = true }: Props) => { +const TraceQLSearch = ({ + datasource, + query, + onChange, + onClearResults, + app, + addVariablesToOptions = true, + range, +}: Props) => { const styles = useStyles2(getStyles); const [alertText, setAlertText] = useState(); const [error, setError] = useState(null); @@ -74,7 +83,7 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVa useEffect(() => { const fetchTags = async () => { try { - await datasource.languageProvider.start(); + await datasource.languageProvider.start(range, datasource.timeRangeForTags); setIsTagsLoading(false); setAlertText(undefined); } catch (error) { @@ -84,7 +93,7 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVa } }; fetchTags(); - }, [datasource, setAlertText]); + }, [datasource, setAlertText, range, datasource.timeRangeForTags]); useEffect(() => { // Initialize state with configured static filters that already have a value from the config @@ -155,6 +164,8 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVa hideTag={true} query={generateQueryWithoutFilter(findFilter(f.id))} addVariablesToOptions={addVariablesToOptions} + range={range} + timeRangeForTags={datasource.timeRangeForTags} /> ) @@ -179,6 +190,8 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app, addVa isMulti={false} allowCustomValue={false} addVariablesToOptions={addVariablesToOptions} + range={range} + timeRangeForTags={datasource.timeRangeForTags} /> void; query: TempoVariableQuery; datasource: TempoDatasource; + range?: TimeRange; }; -export const TempoVariableQueryEditor = ({ onChange, query, datasource }: TempoVariableQueryEditorProps) => { +export const TempoVariableQueryEditor = ({ onChange, query, datasource, range }: TempoVariableQueryEditorProps) => { const [label, setLabel] = useState(query.label || ''); const [type, setType] = useState(query.type); const [labelOptions, setLabelOptions] = useState>>([]); const [labelQuery, setLabelQuery] = useState(''); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (type === TempoVariableQueryType.LabelValues) { - datasource.labelNamesQuery().then((labelNames: Array<{ text: string }>) => { - setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text }))); - }); + setIsLoading(true); + datasource + .labelNamesQuery(range) + .then((labelNames: Array<{ text: string }>) => { + setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text }))); + setIsLoading(false); + }) + .catch(() => { + setIsLoading(false); + }); } - }, [datasource, query, type]); + }, [datasource, query, type, range]); const options = useMemo(() => { if (labelQuery.length === 0) { @@ -122,6 +131,7 @@ export const TempoVariableQueryEditor = ({ onChange, query, datasource }: TempoV width={32} allowCustomValue virtualized + isLoading={isLoading} /> diff --git a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx index 941bcaadceb..47b79d82f74 100644 --- a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx @@ -25,6 +25,7 @@ import { QuerySettings } from './QuerySettings'; import { ServiceGraphSettings } from './ServiceGraphSettings'; import { StreamingSection } from './StreamingSection'; import { TagLimitSection } from './TagLimitSettings'; +import { TagsTimeRangeSettings } from './TagsTimeRangeSettings'; import { TraceQLSearchSettings } from './TraceQLSearchSettings'; export type ConfigEditorProps = DataSourcePluginOptionsEditorProps; @@ -118,9 +119,21 @@ const ConfigEditor = ({ options, onOptionsChange }: ConfigEditorProps) => { - + + } + > + + +
diff --git a/public/app/plugins/datasource/tempo/configuration/TagsTimeRangeSettings.tsx b/public/app/plugins/datasource/tempo/configuration/TagsTimeRangeSettings.tsx new file mode 100644 index 00000000000..a3c8ee6c823 --- /dev/null +++ b/public/app/plugins/datasource/tempo/configuration/TagsTimeRangeSettings.tsx @@ -0,0 +1,45 @@ +import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data'; +import { Combobox, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui'; + +import { TempoJsonData } from '../types'; + +import { getStyles } from './QuerySettings'; + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export const DEFAULT_TIME_RANGE_FOR_TAGS = 1800; // 60 * 30 + +export function TagsTimeRangeSettings({ options, onOptionsChange }: Props) { + const styles = useStyles2(getStyles); + + const selectOptions = [ + { label: 'Last 30 minutes of selected range', value: DEFAULT_TIME_RANGE_FOR_TAGS }, + { label: 'Last 3 hours of selected range', value: 10800 }, // 60 * 60 * 3 + { label: 'Last 24 hours of selected range', value: 86400 }, // 60 * 60 * 24 + { label: 'Last 3 days of selected range', value: 259200 }, // 60 * 60 * 24 * 3 + { label: 'Last 7 days of selected range', value: 604800 }, // 60 * 60 * 24 * 7 + ]; + + return ( +
+ + + { + updateDatasourcePluginJsonDataOption( + { onOptionsChange, options }, + 'timeRangeForTags', + v?.value ?? DEFAULT_TIME_RANGE_FOR_TAGS + ); + }} + placeholder="Time range for tags" + width={40} + /> + + +
+ ); +} diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index f66520555f5..72c82f356b0 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -1227,7 +1227,17 @@ describe('should provide functionality for ad-hoc filters', () => { }); it('for getTagKeys', async () => { - const response = await datasource.getTagKeys(); + const response = await datasource.getTagKeys({ + filters: [], + timeRange: { + from: dateTime('2021-04-20T15:55:00Z'), + to: dateTime('2021-04-20T15:55:00Z'), + raw: { + from: 'now-15m', + to: 'now', + }, + }, + }); expect(response).toEqual([{ text: 'span.label1' }, { text: 'span.label2' }]); }); diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 6338cf58b98..508f4b043a6 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -11,6 +11,7 @@ import { DataQueryRequest, DataQueryResponse, DataQueryResponseData, + DataSourceGetTagKeysOptions, DataSourceGetTagValuesOptions, DataSourceInstanceSettings, dateTime, @@ -21,6 +22,7 @@ import { ScopedVars, SelectableValue, TestDataSourceResponse, + TimeRange, urlUtil, } from '@grafana/data'; import { NodeGraphOptions, SpanBarOptions, TraceToLogsOptions } from '@grafana/o11y-ds-frontend'; @@ -134,6 +136,8 @@ export class TempoDatasource extends DataSourceWithBackend>(() => []); @@ -178,10 +182,10 @@ export class TempoDatasource extends DataSourceWithBackend> { - await this.languageProvider.fetchTags(); + async labelNamesQuery(range?: TimeRange): Promise> { + await this.languageProvider.start(range, this.timeRangeForTags); const tags = this.languageProvider.getAutocompleteTags(); return tags.filter((tag) => tag !== undefined).map((tag) => ({ text: tag })); } - async labelValuesQuery(labelName?: string): Promise> { + async labelValuesQuery(labelName?: string, range?: TimeRange): Promise> { if (!labelName) { return []; } + await this.languageProvider.start(range, this.timeRangeForTags); + let options; try { // Retrieve the scope of the tag @@ -217,7 +223,11 @@ export class TempoDatasource extends DataSourceWithBackend> { - await this.languageProvider.fetchTags(); + async getTagKeys(options: DataSourceGetTagKeysOptions): Promise> { + await this.languageProvider.fetchTags(this.timeRangeForTags, options.timeRange); const tags = this.languageProvider.tagsV2 || []; return tags .map(({ name, tags }) => @@ -243,15 +253,20 @@ export class TempoDatasource extends DataSourceWithBackend): Promise> { const query = this.languageProvider.generateQueryFromFilters({ adhocFilters: options.filters }); - return this.tagValuesQuery(options.key, query); + return this.tagValuesQuery(options.key, query, options.timeRange); } - async tagValuesQuery(tag: string, query: string): Promise> { + async tagValuesQuery(tag: string, query: string, range?: TimeRange): Promise> { let options; try { // For V2, we need to send scope and tag name, e.g. `span.http.status_code`, // unless the tag has intrinsic scope - options = await this.languageProvider.getOptionsV2(tag, query); + options = await this.languageProvider.getOptionsV2({ + tag, + query, + timeRangeForTags: this.timeRangeForTags, + range, + }); } catch { // For V1, the tag name (e.g. `http.status_code`) is enough options = await this.languageProvider.getOptionsV1(getTagWithoutScope(tag)); @@ -782,6 +797,9 @@ export class TempoDatasource extends DataSourceWithBackend { + return enhanceTraceQlMetricsResponse(response, this.instanceSettings); + }), catchError((error) => { reportTempoQueryMetrics('grafana_traces_traceql_metrics_response', options, { success: false, diff --git a/public/app/plugins/datasource/tempo/language_provider.ts b/public/app/plugins/datasource/tempo/language_provider.ts index 6aa67bb750e..c84406e5fa7 100644 --- a/public/app/plugins/datasource/tempo/language_provider.ts +++ b/public/app/plugins/datasource/tempo/language_provider.ts @@ -1,4 +1,4 @@ -import { AdHocVariableFilter, LanguageProvider, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter, LanguageProvider, SelectableValue, TimeRange } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { VariableFormatID } from '@grafana/schema'; @@ -9,6 +9,7 @@ import { getTagsByScope, getUnscopedTags, } from './SearchTraceQLEditor/utils'; +import { DEFAULT_TIME_RANGE_FOR_TAGS } from './configuration/TagsTimeRangeSettings'; import { TraceqlFilter, TraceqlSearchScope } from './dataquery.gen'; import { TempoDatasource } from './datasource'; import { enumIntrinsics, intrinsicsV1 } from './traceql/traceql'; @@ -20,10 +21,19 @@ export const TAGS_LIMIT = 5000; // Limit maximum options in select dropdowns export const OPTIONS_LIMIT = 1000; +interface GetOptionsV2 { + tag: string; + query?: string; + timeRangeForTags?: number; + range?: TimeRange; +} + export default class TempoLanguageProvider extends LanguageProvider { datasource: TempoDatasource; tagsV1?: string[]; tagsV2?: Scope[]; + private previousRange?: TimeRange; + constructor(datasource: TempoDatasource, initialValues?: any) { super(); @@ -36,9 +46,15 @@ export default class TempoLanguageProvider extends LanguageProvider { return res?.data; }; - start = async () => { - if (!this.startTask) { - this.startTask = this.fetchTags().then(() => { + start = async (range?: TimeRange, timeRangeForTags?: number) => { + // Check if we need to refetch tags due to range changes (minute-level granularity) + const shouldRefetch = this.shouldRefreshLabels(range, this.previousRange); + + if (!this.startTask || shouldRefetch) { + // Store the current range for future comparison + this.previousRange = range; + + this.startTask = this.fetchTags(timeRangeForTags, range).then(() => { return []; }); } @@ -46,14 +62,42 @@ export default class TempoLanguageProvider extends LanguageProvider { return this.startTask; }; + roundMsToMin = (milliseconds: number) => { + return this.roundSecToMin(milliseconds / 1000); + }; + + roundSecToMin = (seconds: number) => { + return Math.floor(seconds / 60); + }; + + shouldRefreshLabels = (range?: TimeRange, prevRange?: TimeRange): boolean => { + if (range && prevRange) { + const sameMinuteFrom = this.roundMsToMin(range.from.valueOf()) === this.roundMsToMin(prevRange.from.valueOf()); + const sameMinuteTo = this.roundMsToMin(range.to.valueOf()) === this.roundMsToMin(prevRange.to.valueOf()); + // If both are same, don't need to refresh + return !(sameMinuteFrom && sameMinuteTo); + } + // If one is defined and the other is not, we should refresh + return prevRange !== range; + }; + getTagsLimit = () => { return this.datasource.instanceSettings.jsonData?.tagLimit || TAGS_LIMIT; }; - async fetchTags() { + async fetchTags(timeRangeForTags?: number, range?: TimeRange) { let v1Resp, v2Resp; + try { - v2Resp = await this.request('/api/v2/search/tags', { limit: this.getTagsLimit() }); + const params: { limit: number; start?: number; end?: number } = { + limit: this.getTagsLimit(), + }; + if (timeRangeForTags && range && timeRangeForTags !== DEFAULT_TIME_RANGE_FOR_TAGS) { + const { start, end } = this.getTimeRangeForTags(timeRangeForTags, range); + params.start = start; + params.end = end; + } + v2Resp = await this.request(`/api/v2/search/tags`, params); } catch (error) { v1Resp = await this.request('/api/search/tags', []); } @@ -144,14 +188,23 @@ export default class TempoLanguageProvider extends LanguageProvider { return options; } - async getOptionsV2(tag: string, query?: string): Promise>> { + async getOptionsV2({ tag, query, timeRangeForTags, range }: GetOptionsV2): Promise>> { const encodedTag = this.encodeTag(tag); - const response = await this.request( - `/api/v2/search/tag/${encodedTag}/values`, - query - ? { q: getTemplateSrv().replace(query, {}, VariableFormatID.Pipe), limit: this.getTagsLimit() } - : { limit: this.getTagsLimit() } - ); + const params: { q?: string; limit: number; start?: number; end?: number } = { + limit: this.getTagsLimit(), + }; + + if (query) { + params.q = getTemplateSrv().replace(query, {}, VariableFormatID.Pipe); + } + + if (timeRangeForTags && range && timeRangeForTags !== DEFAULT_TIME_RANGE_FOR_TAGS) { + const { start, end } = this.getTimeRangeForTags(timeRangeForTags, range); + params.start = start; + params.end = end; + } + + const response = await this.request(`/api/v2/search/tag/${encodedTag}/values`, params); let options: Array> = []; if (response && response.tagValues) { response.tagValues.forEach((v: { type: string; value?: string }) => { @@ -167,6 +220,15 @@ export default class TempoLanguageProvider extends LanguageProvider { return options; } + getTimeRangeForTags = (timeRangeForTags: number, range: TimeRange) => { + // Get tags from the last timeRangeForTags seconds, but don't go before the start of the range + // If timeRangeForTags is 1 hour and your query range is 24 hours, it will fetch tags from the last 1 hour of that 24-hour period + // If timeRangeForTags is larger than the total range duration, it will use the entire available range + const start = Math.max(range.from.unix(), range.to.unix() - timeRangeForTags); + const end = range.to.unix(); + return { start, end }; + }; + /** * Encode (serialize) a given tag for use in a URL. * diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index e91f89ae21e..115ba26cad4 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -2,12 +2,12 @@ "name": "@grafana-plugins/tempo", "description": "Grafana plugin for the Tempo data source.", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/lezer-traceql": "0.0.22", + "@grafana/lezer-traceql": "0.0.23", "@grafana/monaco-logql": "^0.0.8", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/plugin-ui": "0.10.7", @@ -38,14 +38,14 @@ "uuid": "11.1.0" }, "devDependencies": { - "@grafana/plugin-configs": "12.1.0-pre", + "@grafana/plugin-configs": "12.2.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/tempo/resultTransformer.ts b/public/app/plugins/datasource/tempo/resultTransformer.ts index 49a7b0faa9e..01934cc97bf 100644 --- a/public/app/plugins/datasource/tempo/resultTransformer.ts +++ b/public/app/plugins/datasource/tempo/resultTransformer.ts @@ -1,6 +1,7 @@ import { SpanStatus } from '@opentelemetry/api'; import { collectorTypes } from '@opentelemetry/exporter-collector'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { isEqual } from 'lodash'; import { createDataFrame, @@ -474,9 +475,20 @@ export function enhanceTraceQlMetricsResponse( const traceIDField = frame.fields.find((field: Field) => field.name === 'traceId'); if (traceIDField) { const links = getDataLinks(instanceSettings); - traceIDField.config.links = traceIDField.config.links?.length - ? [...traceIDField.config.links, ...links] - : links; + const existingLinks = traceIDField.config.links || []; + + // Filter out links that already exist + const newLinks = links.filter( + (link) => + !existingLinks.some( + (existing: DataLink) => + existing.title === link.title && + existing.internal?.datasourceUid === link.internal?.datasourceUid && + isEqual(existing.internal?.query, link.internal?.query) + ) + ); + + traceIDField.config.links = existingLinks.length ? [...existingLinks, ...newLinks] : newLinks; } return frame; }); diff --git a/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx b/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx index 56d2ad225d5..9e9144776c9 100644 --- a/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx +++ b/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx @@ -88,6 +88,7 @@ export function QueryEditor(props: Props) { onChange={props.onChange} datasource={props.datasource} onRunQuery={props.onRunQuery} + range={props.range} />
void; datasource: TempoDatasource; readOnly?: boolean; + range?: TimeRange; } export function TraceQLEditor(props: Props) { const [alertText, setAlertText] = useState(); const { query, onChange, onRunQuery, placeholder } = props; - const setupAutocompleteFn = useAutocomplete(props.datasource, setAlertText); + const setupAutocompleteFn = useAutocomplete( + props.datasource, + setAlertText, + props.datasource.timeRangeForTags ?? DEFAULT_TIME_RANGE_FOR_TAGS, + props.range + ); const theme = useTheme2(); const styles = getStyles(theme, placeholder); @@ -192,20 +199,34 @@ function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) { * Hook that returns function that will set up monaco autocomplete for the label selector * @param datasource the Tempo datasource instance * @param setAlertText setter for alert's text + * @param timeRangeForTags time range for tags and tag values queries + * @param range time range */ -function useAutocomplete(datasource: TempoDatasource, setAlertText: (text?: string) => void) { +function useAutocomplete( + datasource: TempoDatasource, + setAlertText: (text?: string) => void, + timeRangeForTags: number, + range?: TimeRange +) { // We need the provider ref so we can pass it the label/values data later. This is because we run the call for the // values here but there is additional setup needed for the provider later on. We could run the getSeries() in the // returned function but that is run after the monaco is mounted so would delay the request a bit when it does not // need to. const providerRef = useRef( - new CompletionProvider({ languageProvider: datasource.languageProvider, setAlertText }) + new CompletionProvider({ + languageProvider: datasource.languageProvider, + setAlertText, + timeRangeForTags, + range, + }) ); + const previousRangeRef = useRef(range); + useEffect(() => { const fetchTags = async () => { try { - await datasource.languageProvider.start(); + await datasource.languageProvider.start(range, timeRangeForTags); setAlertText(undefined); } catch (error) { if (error instanceof Error) { @@ -214,7 +235,20 @@ function useAutocomplete(datasource: TempoDatasource, setAlertText: (text?: stri } }; fetchTags(); - }, [datasource, setAlertText]); + }, [datasource, setAlertText, range, timeRangeForTags]); + + useEffect(() => { + const rangeChanged = datasource.languageProvider.shouldRefreshLabels(range, previousRangeRef.current); + + if (rangeChanged) { + providerRef.current.range = range; + previousRangeRef.current = range; + } + }, [range, datasource.languageProvider]); + + useEffect(() => { + providerRef.current.timeRangeForTags = timeRangeForTags; + }, [timeRangeForTags]); const autocompleteDisposeFun = useRef<(() => void) | null>(null); useEffect(() => { diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index 906d96416d6..68af850bc7d 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -1,6 +1,6 @@ import { IMarkdownString, languages } from 'monaco-editor'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, TimeRange } from '@grafana/data'; import { isFetchError } from '@grafana/runtime'; import type { Monaco, monacoTypes } from '@grafana/ui'; @@ -25,6 +25,8 @@ type CompletionItem = MinimalCompletionItem & { interface Props { languageProvider: TempoLanguageProvider; setAlertText: (text?: string) => void; + timeRangeForTags?: number; + range?: TimeRange; } /** @@ -38,11 +40,15 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP languageProvider: TempoLanguageProvider; registerInteractionCommandId: string | null; setAlertText: (text?: string) => void; + timeRangeForTags?: number; + range?: TimeRange; constructor(props: Props) { this.languageProvider = props.languageProvider; this.setAlertText = props.setAlertText; this.registerInteractionCommandId = null; + this.timeRangeForTags = props.timeRangeForTags; + this.range = props.range; } triggerCharacters = ['{', '.', '[', '(', '=', '~', ' ', '"']; @@ -391,14 +397,24 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP this.registerInteractionCommandId = id; } - private async getTagValues(tagName: string, query: string): Promise>> { + private async getTagValues( + tagName: string, + query: string, + timeRangeForTags?: number, + range?: TimeRange + ): Promise>> { let tagValues: Array>; const cacheKey = `${tagName}:${query}`; if (this.cachedValues.hasOwnProperty(cacheKey)) { tagValues = this.cachedValues[cacheKey]; } else { - tagValues = await this.languageProvider.getOptionsV2(tagName, query); + tagValues = await this.languageProvider.getOptionsV2({ + tag: tagName, + query, + timeRangeForTags, + range, + }); this.cachedValues[cacheKey] = tagValues; } return tagValues; @@ -461,7 +477,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP case 'SPANSET_IN_VALUE': let tagValues; try { - tagValues = await this.getTagValues(situation.tagName, situation.query); + tagValues = await this.getTagValues(situation.tagName, situation.query, this.timeRangeForTags, this.range); setAlertText(undefined); } catch (error) { if (isFetchError(error)) { diff --git a/public/app/plugins/datasource/tempo/traceql/traceql.test.ts b/public/app/plugins/datasource/tempo/traceql/traceql.test.ts new file mode 100644 index 00000000000..8f91d637950 --- /dev/null +++ b/public/app/plugins/datasource/tempo/traceql/traceql.test.ts @@ -0,0 +1,444 @@ +import { + languageDefinition, + traceqlGrammar, + operators, + keywordOperators, + stringOperators, + numberOperators, + intrinsics, + scopes, + enumIntrinsics, +} from './traceql'; + +describe('TraceQL grammar', () => { + describe('Language definition', () => { + it('should include all required keywords', () => { + const { keywords } = languageDefinition.def.language; + expect(keywords).toContain('with'); + expect(keywords).toContain('span'); + expect(keywords).toContain('resource'); + expect(keywords).toContain('duration'); + expect(keywords).toContain('status'); + }); + + it('should include with clause keywords and parameters', () => { + const { withClauseKeywords, withParameters } = languageDefinition.def.language; + expect(withClauseKeywords).toContain('with'); + expect(withParameters).toContain('most_recent'); + }); + }); + + describe('Operators', () => { + it('should include all comparison operators', () => { + expect(operators).toContain('='); + expect(operators).toContain('!='); + expect(operators).toContain('>'); + expect(operators).toContain('<'); + expect(operators).toContain('>='); + expect(operators).toContain('<='); + expect(operators).toContain('=~'); + expect(operators).toContain('!~'); + }); + + it('should categorize operators correctly', () => { + expect(keywordOperators).toEqual(['=', '!=']); + expect(stringOperators).toEqual(['=', '!=', '=~', '!~']); + expect(numberOperators).toEqual(['=', '!=', '>', '<', '>=', '<=']); + }); + }); + + describe('Intrinsics and scopes', () => { + it('should include all intrinsics', () => { + expect(intrinsics).toContain('duration'); + expect(intrinsics).toContain('name'); + expect(intrinsics).toContain('status'); + expect(intrinsics).toContain('span:duration'); + expect(intrinsics).toContain('trace:id'); + }); + + it('should include all scopes', () => { + expect(scopes).toContain('event'); + expect(scopes).toContain('instrumentation'); + expect(scopes).toContain('link'); + expect(scopes).toContain('resource'); + expect(scopes).toContain('span'); + }); + + it('should identify enum intrinsics', () => { + expect(enumIntrinsics).toContain('kind'); + expect(enumIntrinsics).toContain('span:kind'); + expect(enumIntrinsics).toContain('status'); + expect(enumIntrinsics).toContain('span:status'); + }); + }); + + describe('TraceQL patterns', () => { + it('should match span-set patterns', () => { + const spanSetRule = traceqlGrammar['span-set']; + expect(spanSetRule).toBeDefined(); + expect(spanSetRule.pattern).toBeDefined(); + const spanSetPattern = spanSetRule.pattern as RegExp; + expect(spanSetPattern.test('{span.name="foo"}')).toBe(true); + expect(spanSetPattern.test('{resource.service.name="bar"}')).toBe(true); + expect(spanSetPattern.test('{duration>1s}')).toBe(true); + }); + + it('should match with-clause patterns', () => { + const withClauseRule = traceqlGrammar['with-clause']; + expect(withClauseRule).toBeDefined(); + expect(withClauseRule.pattern).toBeDefined(); + const withClausePattern = withClauseRule.pattern as RegExp; + expect(withClausePattern.test('with (most_recent=true)')).toBe(true); + expect(withClausePattern.test('with (most_recent=false)')).toBe(true); + expect(withClausePattern.test('with(most_recent=true)')).toBe(true); + expect(withClausePattern.test('with (most_recent = true)')).toBe(true); + }); + + it('should match comment patterns', () => { + const commentRule = traceqlGrammar.comment; + expect(commentRule).toBeDefined(); + expect(commentRule.pattern).toBeDefined(); + const commentPattern = commentRule.pattern as RegExp; + expect(commentPattern.test('// This is a comment')).toBe(true); + expect(commentPattern.test('//Another comment')).toBe(true); + }); + + it('should match number patterns', () => { + const numberRule = traceqlGrammar.number; + expect(numberRule).toBeDefined(); + const numberPattern = numberRule as RegExp; + expect(numberPattern.test('123')).toBe(true); + expect(numberPattern.test('123.45')).toBe(true); + expect(numberPattern.test('-123')).toBe(true); + expect(numberPattern.test('1.23e10')).toBe(true); + }); + + it('should match operator patterns', () => { + const operatorRule = traceqlGrammar.operator; + expect(operatorRule).toBeDefined(); + const operatorPattern = operatorRule as RegExp; + expect(operatorPattern.test('=')).toBe(true); + expect(operatorPattern.test('!=')).toBe(true); + expect(operatorPattern.test('>')).toBe(true); + expect(operatorPattern.test('<')).toBe(true); + expect(operatorPattern.test('>=')).toBe(true); + expect(operatorPattern.test('<=')).toBe(true); + }); + }); + + describe('TraceQL query syntax', () => { + const testCases = [ + // Empty query + { + name: 'empty query', + query: '{}', + shouldMatch: true, + }, + // Basic queries + { + name: 'basic span query with string equality', + query: '{ span.name="test" }', + shouldMatch: true, + }, + { + name: 'basic span query with string inequality', + query: '{ span.name!="test" }', + shouldMatch: true, + }, + { + name: 'basic span query with regex match', + query: '{ span.name=~"test" }', + shouldMatch: true, + }, + { + name: 'basic span query with regex mismatch', + query: '{ span.name!~"test" }', + shouldMatch: true, + }, + { + name: 'basic span query with number equality', + query: '{span.duration=10}', + shouldMatch: true, + }, + { + name: 'basic span query with boolean', + query: '{span.flags.sampled=true}', + shouldMatch: true, + }, + { + name: 'resource query', + query: '{resource.service.name="my-service"}', + shouldMatch: true, + }, + { + name: 'duration query', + query: '{duration>1s}', + shouldMatch: true, + }, + // Structural operators + { + name: 'structural operator child query', + query: '{span.name="parent"} > {span.name="child"}', + shouldMatch: true, + }, + { + name: 'structural operator parent query', + query: '{span.name="parent"} < {span.name="child"}', + shouldMatch: true, + }, + { + name: 'structural operator descendant query', + query: '{span.name="parent"} >> {span.name="child"}', + shouldMatch: true, + }, + { + name: 'structural operator ancestor query', + query: '{span.name="parent"} << {span.name="child"}', + shouldMatch: true, + }, + { + name: 'structural operator sibling query', + query: '{span.name="parent"} ~ {span.name="child"}', + shouldMatch: true, + }, + // Union structure operators + { + name: 'union structural operator child query', + query: '{span.name="parent"} &> {span.name="child"}', + shouldMatch: true, + }, + { + name: 'union structural operator parent query', + query: '{span.name="parent"} &< {span.name="child"}', + shouldMatch: true, + }, + { + name: 'union structural operator descendant query', + query: '{span.name="parent"} &>> {span.name="child"}', + shouldMatch: true, + }, + { + name: 'union structural operator ancestor query', + query: '{span.name="parent"} &<< {span.name="child"}', + shouldMatch: true, + }, + { + name: 'union structural operator sibling query', + query: '{span.name="parent"} &~ {span.name="child"}', + shouldMatch: true, + }, + // Negated structure operators + { + name: 'negated structural operator child query', + query: '{span.name="parent"} !> {span.name="child"}', + shouldMatch: true, + }, + { + name: 'negated structural operator parent query', + query: '{span.name="parent"} !< {span.name="child"}', + shouldMatch: true, + }, + { + name: 'negated structural operator descendant query', + query: '{span.name="parent"} !>> {span.name="child"}', + shouldMatch: true, + }, + { + name: 'negated structural operator ancestor query', + query: '{span.name="parent"} !<< {span.name="child"}', + shouldMatch: true, + }, + { + name: 'negated structural operator sibling query', + query: '{span.name="parent"} !~ {span.name="child"}', + shouldMatch: true, + }, // Comments + { + name: 'query with comment', + query: '// Find slow requests\n{duration>1s}', + shouldMatch: true, + }, + // Query hint queries + { + name: 'query hint - most_recent true', + query: '{span.name="test"} with (most_recent=true)', + shouldMatch: true, + }, + { + name: 'query hint - most_recent false', + query: '{span.name="test"} with (most_recent=false)', + shouldMatch: true, + }, + { + name: 'query hint - no spaces', + query: '{span.name="test"} with(most_recent=true)', + shouldMatch: true, + }, + { + name: 'query hint - extra spaces', + query: '{span.name="test"} with ( most_recent = true )', + shouldMatch: true, + }, + // Test enum intrinsics with valid values + { + name: 'enum intrinsic - kind with server value', + query: '{kind=server}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - kind with client value', + query: '{kind=client}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - kind with producer value', + query: '{kind=producer}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - kind with consumer value', + query: '{kind=consumer}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - kind with internal value', + query: '{kind=internal}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - status with ok value', + query: '{status=ok}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - status with error value', + query: '{status=error}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - status with unset value', + query: '{status=unset}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - span:kind with server value', + query: '{span:kind=server}', + shouldMatch: true, + }, + { + name: 'enum intrinsic - span:status with error value', + query: '{span:status=error}', + shouldMatch: true, + }, + // Complex queries + { + name: 'complex query with with clause', + query: '{span.http.status_code=200 && span.name="GET /api"} | select(span.duration) with (most_recent=true)', + shouldMatch: true, + }, + { + name: 'aggregation with with clause', + query: '{span.service.name="frontend"} | avg(duration) with (most_recent=false)', + shouldMatch: true, + }, + ]; + + testCases.forEach(({ name, query, shouldMatch }) => { + it(`should ${shouldMatch ? 'match' : 'not match'} ${name}`, () => { + const grammar = traceqlGrammar; + const spanSetPattern = grammar['span-set']?.pattern as RegExp; + const withClausePattern = grammar['with-clause']?.pattern as RegExp; + const commentPattern = grammar.comment?.pattern as RegExp; + + const spanSetMatches = spanSetPattern ? (query.match(spanSetPattern) || []).length > 0 : false; + const withClauseMatches = withClausePattern ? (query.match(withClausePattern) || []).length > 0 : false; + const commentMatches = commentPattern ? (query.match(commentPattern) || []).length > 0 : false; + + const hasAnyMatch = spanSetMatches || withClauseMatches || commentMatches; + + if (shouldMatch) { + expect(hasAnyMatch).toBe(true); + } else { + expect(hasAnyMatch).toBe(false); + } + }); + }); + }); + + describe('With clause validation', () => { + it('should validate with clause parameter names', () => { + const grammar = traceqlGrammar; + const withClause = grammar['with-clause']; + expect(withClause).toBeDefined(); + expect(withClause.inside).toBeDefined(); + + const parameterNameRule = withClause.inside['parameter-name']; + expect(parameterNameRule).toBeDefined(); + const parameterNamePattern = parameterNameRule.pattern as RegExp; + + expect(parameterNamePattern.test('most_recent=')).toBe(true); + expect(parameterNamePattern.test('invalid_param=')).toBe(true); + expect(parameterNamePattern.test('123invalid=')).toBe(false); + }); + + it('should validate with clause parameter values', () => { + const grammar = traceqlGrammar; + const withClause = grammar['with-clause']; + expect(withClause).toBeDefined(); + expect(withClause.inside).toBeDefined(); + + const parameterValueRule = withClause.inside['parameter-value']; + expect(parameterValueRule).toBeDefined(); + const parameterValuePattern = parameterValueRule.pattern as RegExp; + + expect(parameterValuePattern.test('true')).toBe(true); + expect(parameterValuePattern.test('false')).toBe(true); + expect(parameterValuePattern.test('"string_value"')).toBe(true); + expect(parameterValuePattern.test("'string_value'")).toBe(true); + expect(parameterValuePattern.test('123')).toBe(true); + expect(parameterValuePattern.test('123.45')).toBe(true); + }); + + it('should validate with clause keyword', () => { + const grammar = traceqlGrammar; + const withClause = grammar['with-clause']; + expect(withClause).toBeDefined(); + expect(withClause.inside).toBeDefined(); + + const keywordRule = withClause.inside['with-keyword']; + expect(keywordRule).toBeDefined(); + const keywordPattern = keywordRule.pattern as RegExp; + + expect(keywordPattern.test('with')).toBe(true); + expect(keywordPattern.test('WITH')).toBe(false); // Case sensitive + expect(keywordPattern.test('width')).toBe(false); + }); + }); + + describe('Edge cases', () => { + it('should handle multiple with clauses (invalid but should not crash)', () => { + const query = '{span.name="test"} with (most_recent=true) with (other=false)'; + const grammar = traceqlGrammar; + const withClausePattern = grammar['with-clause']?.pattern as RegExp; + + if (withClausePattern) { + // Use global flag to match all occurrences + const globalPattern = new RegExp(withClausePattern.source, 'g'); + const matches = query.match(globalPattern); + expect(matches).not.toBeNull(); + expect(matches!.length).toBe(2); + } + }); + + it('should handle with clause without parameters', () => { + const query = '{span.name="test"} with ()'; + const grammar = traceqlGrammar; + const withClausePattern = grammar['with-clause']?.pattern as RegExp; + + if (withClausePattern) { + const matches = query.match(withClausePattern); + expect(matches).not.toBeNull(); + } + }); + }); +}); diff --git a/public/app/plugins/datasource/tempo/traceql/traceql.ts b/public/app/plugins/datasource/tempo/traceql/traceql.ts index 00f54a0d25f..eeb7d52101f 100644 --- a/public/app/plugins/datasource/tempo/traceql/traceql.ts +++ b/public/app/plugins/datasource/tempo/traceql/traceql.ts @@ -49,6 +49,7 @@ export const intrinsics = intrinsicsV1.concat([ 'span:id', 'span:kind', 'span:name', + 'span:parentID', 'span:status', 'span:statusMessage', 'trace:duration', @@ -74,7 +75,11 @@ const functions = aggregatorFunctions.concat([ 'select', ]); -const keywords = intrinsics.concat(scopes); +// Add with clause keywords and parameters +const withClauseKeywords = ['with']; +const withParameters = ['most_recent']; + +const keywords = intrinsics.concat(scopes).concat(withClauseKeywords); const statusValues = ['ok', 'unset', 'error', 'false', 'true']; @@ -87,6 +92,8 @@ const language: languages.IMonarchLanguage = { operators, statusValues, functions, + withClauseKeywords, + withParameters, symbols: /[=>?|<|>)?|>[>=]?|`, 'i'), punctuation: /[{};()`,.]/, -}; +} satisfies Grammar; diff --git a/public/app/plugins/datasource/tempo/types.ts b/public/app/plugins/datasource/tempo/types.ts index 346195dcb4a..502f64ab8a7 100644 --- a/public/app/plugins/datasource/tempo/types.ts +++ b/public/app/plugins/datasource/tempo/types.ts @@ -26,6 +26,7 @@ export interface TempoJsonData extends DataSourceJsonData { streamingEnabled?: { search?: boolean; }; + timeRangeForTags?: number; } export interface TempoQuery extends TempoBase { diff --git a/public/app/plugins/datasource/tempo/variables.ts b/public/app/plugins/datasource/tempo/variables.ts index 29c76129ca7..b6f28583543 100644 --- a/public/app/plugins/datasource/tempo/variables.ts +++ b/public/app/plugins/datasource/tempo/variables.ts @@ -18,7 +18,7 @@ export class TempoVariableSupport extends CustomVariableSupport ({ data }))); } } diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index afed09b74d9..126bcc8d923 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/zipkin", "description": "Zipkin plugin for Grafana", "private": true, - "version": "12.1.0-pre", + "version": "12.2.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", @@ -23,10 +23,10 @@ "@grafana/plugin-configs": "workspace:*", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.2.0", + "@testing-library/react": "16.3.0", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.15.0", + "@types/node": "22.16.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.test.ts b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.test.ts new file mode 100644 index 00000000000..613f9983fbf --- /dev/null +++ b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.test.ts @@ -0,0 +1,271 @@ +import Feature from 'ol/Feature'; +import { Point, Polygon } from 'ol/geom'; +import VectorSource from 'ol/source/Vector'; + +import { FieldType, createDataFrame } from '@grafana/data'; + +import { updateFeaturePropertiesForTooltip } from './geojsonDynamic'; + +describe('Dynamic GeoJSON Layer', () => { + describe('updateFeaturePropertiesForTooltip', () => { + let source: VectorSource; + let idToIdx: Map; + let mockFeature1: Feature; + let mockFeature2: Feature; + let mockFeature3: Feature; + let forEachFeatureSpy: jest.SpyInstance; + + beforeEach(() => { + source = new VectorSource(); + idToIdx = new Map(); + + // Create mock features with different IDs + mockFeature1 = new Feature({ + geometry: new Point([0, 0]), + }); + mockFeature1.setId('feature1'); + + mockFeature2 = new Feature({ + geometry: new Polygon([[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]]), + }); + mockFeature2.setId('feature2'); + + mockFeature3 = new Feature({ + geometry: new Point([2, 2]), + }); + mockFeature3.setId('feature3'); + + // Add features to source + source.addFeature(mockFeature1); + source.addFeature(mockFeature2); + source.addFeature(mockFeature3); + + // Set up spy for forEachFeature method + forEachFeatureSpy = jest.spyOn(source, 'forEachFeature'); + }); + + afterEach(() => { + source.clear(); + idToIdx.clear(); + forEachFeatureSpy.mockRestore(); + }); + + it('should update feature properties when frame and idField are provided', () => { + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1', 'feature2', 'feature3'] }, + { name: 'value', type: FieldType.number, values: [10, 20, 30] }, + { name: 'name', type: FieldType.string, values: ['First', 'Second', 'Third'] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Check that idToIdx map is populated correctly + expect(idToIdx.get('feature1')).toBe(0); + expect(idToIdx.get('feature2')).toBe(1); + expect(idToIdx.get('feature3')).toBe(2); + + // Check that features have frame and rowIndex properties set + expect(mockFeature1.get('frame')).toBe(frame); + expect(mockFeature1.get('rowIndex')).toBe(0); + + expect(mockFeature2.get('frame')).toBe(frame); + expect(mockFeature2.get('rowIndex')).toBe(1); + + expect(mockFeature3.get('frame')).toBe(frame); + expect(mockFeature3.get('rowIndex')).toBe(2); + }); + + it('should handle partial matches between features and frame data', () => { + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1', 'feature3'] }, // missing feature2 + { name: 'value', type: FieldType.number, values: [10, 30] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Check that only matching features have properties set + expect(mockFeature1.get('frame')).toBe(frame); + expect(mockFeature1.get('rowIndex')).toBe(0); + + expect(mockFeature2.get('frame')).toBeUndefined(); + expect(mockFeature2.get('rowIndex')).toBeUndefined(); + + expect(mockFeature3.get('frame')).toBe(frame); + expect(mockFeature3.get('rowIndex')).toBe(1); + }); + + it('should handle features with no matching data', () => { + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['nonexistent1', 'nonexistent2'] }, + { name: 'value', type: FieldType.number, values: [10, 20] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Check that no features have properties set since IDs don't match + expect(mockFeature1.get('frame')).toBeUndefined(); + expect(mockFeature1.get('rowIndex')).toBeUndefined(); + + expect(mockFeature2.get('frame')).toBeUndefined(); + expect(mockFeature2.get('rowIndex')).toBeUndefined(); + + expect(mockFeature3.get('frame')).toBeUndefined(); + expect(mockFeature3.get('rowIndex')).toBeUndefined(); + }); + + it('should clear idToIdx map on each call', () => { + // First call + const frame1 = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1'] }, + { name: 'value', type: FieldType.number, values: [10] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame1, 'id', idToIdx); + expect(idToIdx.size).toBe(1); + expect(idToIdx.get('feature1')).toBe(0); + + // Second call with different data + const frame2 = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature2', 'feature3'] }, + { name: 'value', type: FieldType.number, values: [20, 30] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame2, 'id', idToIdx); + + // Map should be cleared and repopulated + expect(idToIdx.size).toBe(2); + expect(idToIdx.get('feature1')).toBeUndefined(); + expect(idToIdx.get('feature2')).toBe(0); + expect(idToIdx.get('feature3')).toBe(1); + }); + + it('should handle features with null or undefined IDs', () => { + // Add a feature with no ID + const featureWithoutId = new Feature({ + geometry: new Point([3, 3]), + }); + source.addFeature(featureWithoutId); + + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1', 'feature2'] }, + { name: 'value', type: FieldType.number, values: [10, 20] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Features with IDs should be processed + expect(mockFeature1.get('frame')).toBe(frame); + expect(mockFeature1.get('rowIndex')).toBe(0); + + // Feature without ID should not be processed + expect(featureWithoutId.get('frame')).toBeUndefined(); + expect(featureWithoutId.get('rowIndex')).toBeUndefined(); + }); + + it('should handle string and number IDs', () => { + // Add features with number IDs + const featureWithNumberId = new Feature({ + geometry: new Point([4, 4]), + }); + featureWithNumberId.setId(123); + source.addFeature(featureWithNumberId); + + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.number, values: [123, 456] }, + { name: 'value', type: FieldType.number, values: [100, 200] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Feature with number ID should be processed (converted to string) + expect(featureWithNumberId.get('frame')).toBe(frame); + expect(featureWithNumberId.get('rowIndex')).toBe(0); + }); + + it('should return early when frame is undefined', () => { + updateFeaturePropertiesForTooltip(source, undefined, 'id', idToIdx); + + expect(forEachFeatureSpy).not.toHaveBeenCalled(); + expect(idToIdx.size).toBe(0); + }); + + it('should return early when idField is undefined', () => { + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1'] }, + { name: 'value', type: FieldType.number, values: [10] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, undefined, idToIdx); + + expect(forEachFeatureSpy).not.toHaveBeenCalled(); + expect(idToIdx.size).toBe(0); + }); + + it('should return early when idField is empty string', () => { + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1'] }, + { name: 'value', type: FieldType.number, values: [10] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, '', idToIdx); + + expect(forEachFeatureSpy).not.toHaveBeenCalled(); + expect(idToIdx.size).toBe(0); + }); + + it('should handle when idField does not exist in frame', () => { + const frame = createDataFrame({ + fields: [ + { name: 'different_field', type: FieldType.string, values: ['feature1'] }, + { name: 'value', type: FieldType.number, values: [10] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'nonexistent_field', idToIdx); + + expect(forEachFeatureSpy).not.toHaveBeenCalled(); + expect(idToIdx.size).toBe(0); + }); + + it('should preserve existing feature properties while adding tooltip properties', () => { + // Set some existing properties on the feature + mockFeature1.set('existingProperty', 'existing value'); + mockFeature1.set('anotherProperty', 42); + + const frame = createDataFrame({ + fields: [ + { name: 'id', type: FieldType.string, values: ['feature1'] }, + { name: 'value', type: FieldType.number, values: [10] }, + ], + }); + + updateFeaturePropertiesForTooltip(source, frame, 'id', idToIdx); + + // Check that existing properties are preserved + expect(mockFeature1.get('existingProperty')).toBe('existing value'); + expect(mockFeature1.get('anotherProperty')).toBe(42); + + // Check that tooltip properties are added + expect(mockFeature1.get('frame')).toBe(frame); + expect(mockFeature1.get('rowIndex')).toBe(0); + }); + }); +}); diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts index b09aa0a2576..53bab286a52 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts @@ -15,6 +15,7 @@ import { GrafanaTheme2, PluginState, EventBus, + DataFrame, } from '@grafana/data'; import { ComparisonOperation } from '@grafana/schema'; import { findField } from 'app/features/dimensions/utils'; @@ -65,6 +66,14 @@ export const DEFAULT_STYLE_RULE: FeatureStyleConfig = { }, }; +// Default configuration with tooltip support enabled +export const defaultDynamicGeoJSONConfig: MapLayerOptions = { + type: 'dynamic-geojson', + name: 'Dynamic GeoJSON', + config: defaultOptions, + tooltip: true, +}; + export const dynamicGeoJSONLayer: MapLayerRegistryItem = { id: 'dynamic-geojson', name: 'Dynamic GeoJSON', @@ -88,11 +97,20 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem(); + // Function to update feature properties for tooltip support + const updateFeatureProperties = (frame?: DataFrame) => { + updateFeaturePropertiesForTooltip(source, frame, config.idField, idToIdx); + }; + const key = source.on('change', () => { //one geojson loads if (source.getState() === 'ready') { unByKey(key); features.next(source.getFeatures()); + // Apply current data to newly loaded features + if (currentFrame) { + updateFeatureProperties(currentFrame); + } } }); @@ -117,14 +135,16 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem(); + let currentFrame: DataFrame | undefined = undefined; const vectorLayer = new VectorImage({ source, style: (feature: FeatureLike) => { - const idx = idToIdx.get(feature.getId() as string); + const featureId = feature.getId(); + const idx = featureId != null ? idToIdx.get(String(featureId)) : undefined; const dims = style.dims; - if (idx && dims) { + if (idx !== undefined && dims) { return new Style({ fill: new Fill({ color: dims.color?.get(idx) }), stroke: new Stroke({ color: style.base.color, width: style.base.lineWidth ?? 1 }), @@ -174,12 +194,12 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem { const frame = data.series[0]; if (frame) { - const field = findField(frame, config.idField); - if (field) { - idToIdx.clear(); - field.values.forEach((v, i) => idToIdx.set(v, i)); - } - + currentFrame = frame; + + // Update feature properties for tooltip support + updateFeatureProperties(frame); + + // Update style dimensions for data-driven styling style.dims = getStyleDimension(frame, style, theme, config.dataStyle); } vectorLayer.changed(); @@ -232,3 +252,39 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem +): void { + if (!frame || !idField) { + return; + } + + const field = findField(frame, idField); + if (field) { + idToIdx.clear(); + field.values.forEach((v, i) => idToIdx.set(String(v), i)); + + source.forEachFeature((feature) => { + const featureId = feature.getId(); + if (featureId != null) { + const rowIndex = idToIdx.get(String(featureId)); + if (rowIndex !== undefined) { + // Set tooltip properties without overwriting existing GeoJSON properties + feature.set('frame', frame); + feature.set('rowIndex', rowIndex); + } + } + }); + } +} diff --git a/public/app/plugins/panel/logs-new/LogsPanel.test.tsx b/public/app/plugins/panel/logs-new/LogsPanel.test.tsx index 77570bf19ab..09997a66047 100644 --- a/public/app/plugins/panel/logs-new/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs-new/LogsPanel.test.tsx @@ -18,6 +18,11 @@ import { LogsPanel } from './LogsPanel'; type LogsPanelProps = ComponentProps; +jest.mock('@grafana/assistant', () => ({ + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn(() => [true, jest.fn()]), +})); + jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getAppEvents: jest.fn(), diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx index 6a299326f85..64ef4a60a44 100644 --- a/public/app/plugins/panel/logs/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx @@ -57,6 +57,13 @@ jest.mock('@grafana/data', () => ({ hasLogsContextSupport: (ds: MockDataSourceApi) => hasLogsContextSupport(ds), })); +jest.mock('@grafana/assistant', () => { + return { + ...jest.requireActual('@grafana/assistant'), + useAssistant: jest.fn().mockReturnValue([true, jest.fn()]), + }; +}); + const defaultProps = { data: { error: undefined, diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 03f2ffbfc35..ca44d725502 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -164,6 +164,7 @@ export const LogsPanel = ({ fontSize, syntaxHighlighting, detailsMode: detailsModeProp, + noInteractions, ...options }, id, @@ -566,6 +567,7 @@ export const LogsPanel = ({ logs={deduplicatedRows} logSupportsContext={showContextToggle} loadMore={enableInfiniteScrolling ? loadMoreLogs : undefined} + noInteractions={noInteractions} onClickFilterLabel={ isOnClickFilterLabel(onClickFilterLabel) ? onClickFilterLabel : defaultOnClickFilterLabel } diff --git a/public/app/plugins/panel/logs/panelcfg.cue b/public/app/plugins/panel/logs/panelcfg.cue index 5f7ac972318..1f8f16fe13b 100644 --- a/public/app/plugins/panel/logs/panelcfg.cue +++ b/public/app/plugins/panel/logs/panelcfg.cue @@ -39,6 +39,7 @@ composableKinds: PanelCfg: { sortOrder: common.LogsSortOrder dedupStrategy: common.LogsDedupStrategy enableInfiniteScrolling?: bool + noInteractions?: bool fontSize?: "default" | "small" @cuetsy(kind="enum", memberNames="default|small") detailsMode?: "inline" | "sidebar" @cuetsy(kind="enum", memberNames="inline|sidebar") // TODO: figure out how to define callbacks diff --git a/public/app/plugins/panel/logs/panelcfg.gen.ts b/public/app/plugins/panel/logs/panelcfg.gen.ts index 3d28ef5ff55..ef4c192dbe2 100644 --- a/public/app/plugins/panel/logs/panelcfg.gen.ts +++ b/public/app/plugins/panel/logs/panelcfg.gen.ts @@ -22,6 +22,7 @@ export interface Options { logLineMenuCustomItems?: unknown; logRowMenuIconsAfter?: unknown; logRowMenuIconsBefore?: unknown; + noInteractions?: boolean; /** * TODO: figure out how to define callbacks */ diff --git a/public/app/plugins/panel/table/table-new/PaginationEditor.tsx b/public/app/plugins/panel/table/table-new/PaginationEditor.tsx index 0bfa20b565b..6434c75e1b9 100644 --- a/public/app/plugins/panel/table/table-new/PaginationEditor.tsx +++ b/public/app/plugins/panel/table/table-new/PaginationEditor.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { StandardEditorProps } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Switch } from '@grafana/ui'; export function PaginationEditor({ onChange, value, context }: StandardEditorProps) { @@ -11,5 +12,11 @@ export function PaginationEditor({ onChange, value, context }: StandardEditorPro onChange(event?.currentTarget.checked); }; - return ; + return ( + + ); } diff --git a/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx index 7213d4f29a2..5aae9d65b7a 100644 --- a/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx +++ b/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx @@ -1,5 +1,6 @@ +import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; -import { TableAutoCellOptions, TableColorTextCellOptions } from '@grafana/schema'; +import { TableAutoCellOptions, TableColoredBackgroundCellOptions, TableColorTextCellOptions } from '@grafana/schema'; import { Field, Switch } from '@grafana/ui'; import { TableCellEditorProps } from '../TableCellOptionEditor'; @@ -7,9 +8,8 @@ import { TableCellEditorProps } from '../TableCellOptionEditor'; export const AutoCellOptionsEditor = ({ cellOptions, onChange, -}: TableCellEditorProps) => { +}: TableCellEditorProps) => { // Handle row coloring changes - const onWrapTextChange = () => { cellOptions.wrapText = !cellOptions.wrapText; onChange(cellOptions); @@ -17,7 +17,11 @@ export const AutoCellOptionsEditor = ({ return ( - + ); }; diff --git a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx index b1ecbfc4862..b7d5d2ab3dc 100644 --- a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx +++ b/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx @@ -1,10 +1,13 @@ import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { TableCellBackgroundDisplayMode, TableColoredBackgroundCellOptions } from '@grafana/schema'; import { Field, RadioButtonGroup, Switch } from '@grafana/ui'; import { TableCellEditorProps } from '../TableCellOptionEditor'; +import { AutoCellOptionsEditor } from './AutoCellOptionsEditor'; + const colorBackgroundOpts: Array> = [ { value: TableCellBackgroundDisplayMode.Basic, label: 'Basic' }, { value: TableCellBackgroundDisplayMode.Gradient, label: 'Gradient' }, @@ -14,7 +17,6 @@ export const ColorBackgroundCellOptionsEditor = ({ onChange, }: TableCellEditorProps) => { // Set the display mode on change - const onCellOptionsChange = (v: TableCellBackgroundDisplayMode) => { cellOptions.mode = v; onChange(cellOptions); @@ -25,23 +27,19 @@ export const ColorBackgroundCellOptionsEditor = ({ onChange(cellOptions); }; - // Handle row coloring changes - const onWrapTextChange = () => { - cellOptions.wrapText = !cellOptions.wrapText; - onChange(cellOptions); - }; - return ( <> + - - - - + + + { + cellOptions.wrapText = updatedCellOptions.wrapText; + onChange(cellOptions); + }} + /> ); }; diff --git a/public/app/plugins/panel/timeseries/module.tsx b/public/app/plugins/panel/timeseries/module.tsx index beccdf480d6..0c909935145 100644 --- a/public/app/plugins/panel/timeseries/module.tsx +++ b/public/app/plugins/panel/timeseries/module.tsx @@ -1,5 +1,6 @@ import { PanelPlugin } from '@grafana/data'; import { t } from '@grafana/i18n'; +import { config } from '@grafana/runtime'; import { commonOptionsBuilder } from '@grafana/ui'; import { optsWithHideZeros } from '@grafana/ui/internal'; @@ -17,6 +18,10 @@ export const plugin = new PanelPlugin(TimeSeriesPanel) commonOptionsBuilder.addTooltipOptions(builder, false, true, optsWithHideZeros); commonOptionsBuilder.addLegendOptions(builder); + if (config.featureToggles.timeComparison) { + commonOptionsBuilder.addTimeCompareOption(builder); + } + builder.addCustomEditor({ id: 'timezone', name: t('timeseries.name-time-zone', 'Time zone'), diff --git a/public/app/plugins/panel/timeseries/panelcfg.cue b/public/app/plugins/panel/timeseries/panelcfg.cue index 0d2f6decd67..71ac34bd897 100644 --- a/public/app/plugins/panel/timeseries/panelcfg.cue +++ b/public/app/plugins/panel/timeseries/panelcfg.cue @@ -25,6 +25,7 @@ composableKinds: PanelCfg: lineage: { Options: common.OptionsWithTimezones & { legend: common.VizLegendOptions tooltip: common.VizTooltipOptions + timeCompare?: common.TimeCompareOptions orientation?: common.VizOrientation } @cuetsy(kind="interface") diff --git a/public/app/plugins/panel/timeseries/panelcfg.gen.ts b/public/app/plugins/panel/timeseries/panelcfg.gen.ts index e5e4bce7e7b..95eb3a710f8 100644 --- a/public/app/plugins/panel/timeseries/panelcfg.gen.ts +++ b/public/app/plugins/panel/timeseries/panelcfg.gen.ts @@ -13,6 +13,7 @@ import * as common from '@grafana/schema'; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; orientation?: common.VizOrientation; + timeCompare?: common.TimeCompareOptions; tooltip: common.VizTooltipOptions; } diff --git a/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx b/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx index 89e5c8f7c08..33d6eb0e013 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx @@ -101,7 +101,9 @@ export const AnnotationTooltip2 = ({ annoVals, annoIdx, timeZone, onEdit }: Prop {alertText}
- {annoVals.tags?.[annoIdx]?.map((t: string, i: number) => )} + {annoVals.tags?.[annoIdx]?.map((t: string, i: number) => ( + + ))}
diff --git a/public/app/plugins/panel/xychart/SeriesEditor.tsx b/public/app/plugins/panel/xychart/SeriesEditor.tsx index a68cf297ac3..54bc8085154 100644 --- a/public/app/plugins/panel/xychart/SeriesEditor.tsx +++ b/public/app/plugins/panel/xychart/SeriesEditor.tsx @@ -256,7 +256,6 @@ export const SeriesEditor = ({ settings: { // TODO: filter out series.y?.exclude.options, series.size.matcher.options, series.color.matcher.options filter: (field) => - field.name !== series.x?.matcher.options && (mapping === SeriesMapping.Auto || field.state?.origin?.frameIndex === series.frame?.matcher.options) && field.type === FieldType.number && @@ -292,7 +291,6 @@ export const SeriesEditor = ({ settings: { // TODO: filter out series.y?.exclude.options, series.size.matcher.options, series.color.matcher.options filter: (field) => - field.name !== series.x?.matcher.options && (mapping === SeriesMapping.Auto || field.state?.origin?.frameIndex === series.frame?.matcher.options) && field.type === FieldType.number && diff --git a/public/app/plugins/panel/xychart/XYChartTooltip.tsx b/public/app/plugins/panel/xychart/XYChartTooltip.tsx index f72a67b7b5e..585910e9184 100644 --- a/public/app/plugins/panel/xychart/XYChartTooltip.tsx +++ b/public/app/plugins/panel/xychart/XYChartTooltip.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; -import { colorManipulator, DataFrame, InterpolateFunction, LinkModel } from '@grafana/data'; +import { colorManipulator, DataFrame, Field, InterpolateFunction, LinkModel } from '@grafana/data'; import { VizTooltipContent, VizTooltipFooter, @@ -34,6 +34,14 @@ function stripSeriesName(fieldName: string, seriesName: string) { return fieldName; } +function hideFromTooltip(field: Field) { + return field.config.custom.hideFrom?.tooltip ?? false; +} + +function getFieldName(field: Field) { + return field.state?.displayName ?? field.name; +} + export const XYChartTooltip = ({ dataIdxs, seriesIdx, @@ -55,52 +63,64 @@ export const XYChartTooltip = ({ let label = series.name.value; - let seriesColor = series.color.fixed; - // let colorField = series.color.field; - // let pointColor: string; + let seriesColor = colorField?.display?.(colorField.values[rowIndex]).color ?? series.color.fixed ?? '#fff'; + let fillOpacity = colorField?.config.custom?.fillOpacity; - // if (colorField != null) { - // pointColor = colorField.display?.(colorField.values[rowIndex]).color!; - // } + // TODO: skip this if seriesColor already has an alpha component, such as opacity-by-value or opacity gradient schemes + if (fillOpacity != null) { + seriesColor = colorManipulator.alpha(seriesColor, fillOpacity / 100); + } const headerItem: VizTooltipItem = { label, value: '', - color: colorManipulator.alpha(seriesColor ?? '#fff', 0.5), + color: seriesColor, colorIndicator: ColorIndicator.marker_md, }; - const contentItems: VizTooltipItem[] = [ - { - label: stripSeriesName(xField.state?.displayName ?? xField.name, label), - value: fmt(xField, xField.values[rowIndex]), - }, - { - label: stripSeriesName(yField.state?.displayName ?? yField.name, label), - value: fmt(yField, yField.values[rowIndex]), - }, - ]; + const contentItems: VizTooltipItem[] = []; + const addedFields = new Set(); - // mapped fields for size/color - if (sizeField != null && sizeField !== yField) { + if (!hideFromTooltip(xField)) { contentItems.push({ - label: stripSeriesName(sizeField.state?.displayName ?? sizeField.name, label), - value: fmt(sizeField, sizeField.values[rowIndex]), + label: stripSeriesName(getFieldName(xField), label), + value: fmt(xField, xField.values[rowIndex]), }); + addedFields.add(xField); } - if (colorField != null && colorField !== yField) { + if (!hideFromTooltip(yField)) { contentItems.push({ - label: stripSeriesName(colorField.state?.displayName ?? colorField.name, label), + label: stripSeriesName(getFieldName(yField), label), + value: fmt(yField, yField.values[rowIndex]), + }); + addedFields.add(yField); + } + + // mapped fields for size/color + if (sizeField != null && !addedFields.has(sizeField) && !hideFromTooltip(sizeField)) { + contentItems.push({ + label: stripSeriesName(getFieldName(sizeField), label), + value: fmt(sizeField, sizeField.values[rowIndex]), + }); + addedFields.add(sizeField); + } + + if (colorField != null && !addedFields.has(colorField) && !hideFromTooltip(colorField)) { + contentItems.push({ + label: stripSeriesName(getFieldName(colorField), label), value: fmt(colorField, colorField.values[rowIndex]), }); + addedFields.add(colorField); } series._rest.forEach((field) => { - contentItems.push({ - label: stripSeriesName(field.state?.displayName ?? field.name, label), - value: fmt(field, field.values[rowIndex]), - }); + if (!hideFromTooltip(field)) { + contentItems.push({ + label: stripSeriesName(field.state?.displayName ?? field.name, label), + value: fmt(field, field.values[rowIndex]), + }); + } }); let footer: ReactNode; diff --git a/public/app/plugins/panel/xychart/utils.ts b/public/app/plugins/panel/xychart/utils.ts index df7fb0bc041..3526f7c9d6d 100644 --- a/public/app/plugins/panel/xychart/utils.ts +++ b/public/app/plugins/panel/xychart/utils.ts @@ -99,14 +99,8 @@ export function prepSeries( // only grabbing number fields (exclude time, string, enum, other) let onlyNumFields = onlyNumTimeFields.filter((field) => field.type === FieldType.number); - let color = - colorMatcher != null - ? onlyNumFields.find((field) => field !== x && colorMatcher!(field, frame, frames)) - : undefined; - let size = - sizeMatcher != null - ? onlyNumFields.find((field) => field !== x && field !== color && sizeMatcher!(field, frame, frames)) - : undefined; + let color = colorMatcher != null ? onlyNumFields.find((field) => colorMatcher(field, frame, frames)) : undefined; + let size = sizeMatcher != null ? onlyNumFields.find((field) => sizeMatcher(field, frame, frames)) : undefined; // x field is required if (x != null) { diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 41221a4e772..334087cfbed 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -301,13 +301,9 @@ export function getAppRoutes(): RouteDescriptor[] { { path: '/admin/authentication', roles: () => contextSrv.evaluatePermission([AccessControlAction.SettingsWrite]), - component: - config.licenseInfo.enabledFeatures?.saml || config.ldapEnabled - ? SafeDynamicImport( - () => - import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/AuthProvidersListPage') - ) - : () => , + component: SafeDynamicImport( + () => import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/AuthProvidersListPage') + ), }, { path: '/admin/authentication/ldap', @@ -388,6 +384,7 @@ export function getAppRoutes(): RouteDescriptor[] { // LOGIN / SIGNUP { path: '/login', + allowAnonymous: true, component: SafeDynamicImport( () => import(/* webpackChunkName: "LoginPage" */ 'app/core/components/Login/LoginPage') ), @@ -413,6 +410,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/signup', + allowAnonymous: true, component: config.disableUserSignUp ? () => : SafeDynamicImport(() => import(/* webpackChunkName "SignupPage"*/ 'app/core/components/Signup/SignupPage')), @@ -421,6 +419,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/user/password/send-reset-email', + allowAnonymous: true, chromeless: true, component: SafeDynamicImport( () => @@ -429,6 +428,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/user/password/reset', + allowAnonymous: true, component: SafeDynamicImport( () => import( @@ -477,6 +477,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/sandbox/test', + allowAnonymous: true, // purposefully to allow testing component: SafeDynamicImport( () => import(/* webpackChunkName: "TestStuffPage"*/ 'app/features/sandbox/TestStuffPage') ), diff --git a/public/img/icons/unicons/bitbucket.svg b/public/img/icons/unicons/bitbucket.svg new file mode 100644 index 00000000000..c0995016e49 --- /dev/null +++ b/public/img/icons/unicons/bitbucket.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/img/icons/unicons/git.svg b/public/img/icons/unicons/git.svg new file mode 100644 index 00000000000..44d7438c613 --- /dev/null +++ b/public/img/icons/unicons/git.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 495f3ed7840..9b064e0918f 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1563,7 +1564,8 @@ "title": "Upravit hodnotící skupinu" }, "group-loader": { - "group-load-failed": "Nepodařilo se načíst pravidla ze skupiny {{ groupName }} v jmenném prostoru {{ namespaceName }}" + "group-load-failed": "Nepodařilo se načíst pravidla ze skupiny {{ groupName }} v jmenném prostoru {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Probíhá odstranění skupiny" @@ -2486,7 +2488,6 @@ }, "rule-list": { "configure-datasource": "Konfigurovat", - "draft-new-rule": "Navrhnout nové pravidlo", "ds-error": { "title": "Nelze načíst pravidla pro tento zdroj dat" }, @@ -2512,6 +2513,7 @@ "new-alert-rule": "Nové pravidlo výstrahy", "new-datasource-recording-rule": "Nové pravidlo nahrávání zdroje dat", "new-grafana-recording-rule": "Nové pravidlo nahrávání Grafany", + "new-rule-for-export": "", "pagination": { "next-page": "Zobrazit více…" }, @@ -2828,6 +2830,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Obnovit náhled", + "empty-state": "", "label-preview": "Náhled", "refresh": "Obnovit" }, @@ -3282,6 +3285,11 @@ "id-token-attribute-name-label": "Název atributu ID tokenu", "login-attribute-path-description": "Výraz JMESPath, který se použije pro vyhledání přihlašovacích údajů uživatele z tokenu ID uživatele.", "login-attribute-path-label": "Cesta atributu přihlášení", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "ID klienta spravované identity federovaných identifikačních údajů vaší aplikace OAuth2.", "managed-identity-client-id-label": "ID klienta spravované identity FIC", "name-attribute-path-description": "Výraz JMESPath, který se použije pro vyhledání uživatelského jména z tokenu ID uživatele.\nToto jméno bude použito jako zobrazované jméno uživatele.", @@ -3380,6 +3388,9 @@ "text-badge-enabled": "Povoleno", "text-badge-not-enabled": "Není povoleno" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Koncový bod .well-known / openid-configuration pro vašeho poskytovatele identity", "title-open-id-connect-discovery-url": "Adresa URL OpenID Connect Discovery" @@ -3497,7 +3508,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Zrušit", "cannot-move-folders": "Složky nelze přesunout", "confirmation-text": "Odstranit", @@ -3530,6 +3540,26 @@ "browse-view": { "this-folder-is-empty": "Tato složka je prázdná" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_few": "", + "failed-alert_many": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} pravidlo výstrahy", "alertRule_few": "{{count}} pravidlo výstrahy", @@ -3590,6 +3620,7 @@ "empty-message": "Nebyly nalezeny žádné složky", "error-title": "Chyba při načítání složek", "non-folder-item": "Bez složky {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Vyhledat složky", "unknown-error": "Neznámá chyba" }, @@ -4573,6 +4604,10 @@ }, "edit-actions": { "add": "Přidat {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "Přesunout {{typeName}}", "panel-background": "Změnit pozadí panelu", "panel-description": "Změnit popis panelu", @@ -4587,6 +4622,7 @@ "edit-pane": { "elements": { "dashboard": "Nástěnka", + "element": "", "local-variable": "Lokální proměnná", "multiple-elements": "Více prvků", "multiple-elements-delete-text": "Opravdu chcete odstranit tyto prvky?", @@ -5800,7 +5836,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5966,8 +6001,8 @@ "title-someone-else-has-updated-this-dashboard": "Tuto nástěnku aktualizoval jiný uživatel", "would-still-dashboard": "Chcete přesto tuto nástěnku uložit?" }, - "schema-v2-library-panels-export": "Funkce dynamické nástěnky panelu je experimentální a nemá plnou funkčnost srovnatelnou se současným chováním nástěnek. Je založena na novém formátu schématu, který nepodporuje panely knihovny. To znamená, že při exportu nástěnky pro použití v jiné instanci nebudeme zahrnovat panely knihoven. Jejich podpora je naplánována v rámci <2>životního cyklu funkce.", - "schema-v2-library-panels-export-title": "Schéma nástěnky V2 zatím nepodporuje export panelů knihoven pro použití v jiné instanci", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Nástěnka byla nevratně změněna" }, "save-dashboard-form-common-options": { @@ -7173,6 +7208,7 @@ "start-time": "Čas zahájení:" } }, + "share-span": "", "warnings": "Varování" }, "span-filters": { @@ -7752,6 +7788,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7991,6 +8430,60 @@ "previous-label": "Předchozí měsíc", "select-placeholder": "Vyberte datum/čas" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Zavřít" }, @@ -8038,6 +8531,20 @@ "field-name-picker": { "placeholder": "Vyberte pole" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "Pole s názvem odpovídajícím regulárnímu výrazu", "name-fields-by-query": "Pole vrácená dotazem", "name-fields-with-name": "Pole s názvem", @@ -8142,7 +8649,10 @@ "no-data": "" }, "no-values-label": "Žádné hodnoty", - "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} z {{numRows}} řádků" + "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} z {{numRows}} řádků", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tagy" @@ -8891,8 +9401,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "Žádná pole k zobrazení.", + "open-assistant": "", "pin-line": "Připnout protokol", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "Žádné výsledky k zobrazení." }, @@ -8907,6 +9420,9 @@ "copy-log": "Kopírovat řádek protokolu", "hide-details": "Zobrazit podrobnosti protokolu", "icon-label": "Nabídka protokolu", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Připnout protokol", "show-context": "Zobrazit kontext", "show-details": "Skrýt podrobnosti protokolu", @@ -9740,7 +10256,9 @@ "aria-label": "Nápověda" }, "invite-user": { - "invite-new-member-button": "Pozvat nového člena" + "invite-button": "Pozvat", + "invite-new-member-button": "Pozvat nového člena", + "invite-tooltip": "" }, "item": { "add-bookmark": "Přidat do záložek", @@ -10504,17 +11022,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Tato funkce je v současné době v aktivním vývoji. Pro nejlepší zážitek a nejnovější vylepšení doporučujeme použít <2>noční sestavení Grafany." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Pojmenujte jasně toto připojení k úložišti", "empty": "Prázdný", @@ -10524,10 +11064,18 @@ "label-display-name": "Zobrazované jméno", "placeholder-my-repository-connection": "Moje připojení k úložišti", "text-loading-resource-information": "Načítání informací o zdroji…", + "dashboards-count_one": "", + "dashboards-count_few": "", + "dashboards-count_many": "", + "dashboards-count_other": "", "files-count_one": "Počet souborů: {{count}}", "files-count_few": "Počet souborů: {{count}}", "files-count_many": "Počet souborů: {{count}}", - "files-count_other": "Počet souborů: {{count}}" + "files-count_other": "Počet souborů: {{count}}", + "folders-count_one": "", + "folders-count_few": "", + "folders-count_many": "", + "folders-count_other": "" }, "check-repository": { "check": "Kontrola" @@ -10538,36 +11086,20 @@ "button-save": "Uložit", "button-saving": "Ukládání…", "description-enabled": "Jakmile je povoleno automatické tažení, cíl nelze změnit.", - "description-path": "Cesta k dílčímu adresáři v úložišti Git", "description-read-only": "Zdroje nelze upravovat přes Grafanu.", - "description-repository-url": "Zadejte adresu URL úložiště GitHub", "description-title": "Člověkem čitelný název konfigurace", "error-required": "Toto pole je povinné.", "error-save-repository": "Nastavení úložiště se nepodařilo uložit", - "error-valid-github-url": "Zadejte platnou adresu URL úložiště GitHub", "label-automatic-pulling": "Automatické vyžádání", - "label-branch": "Větev", "label-enabled": "Povoleno", - "label-github-token": "Token GitHubu", "label-interval-seconds": "Interval (sekundy)", - "label-local-path": "Místní cesta", - "label-path": "Cesta", - "label-pr-workflow": "Povolit možnost pull request při ukládání", "label-repository-type": "Typ úložiště", - "label-repository-url": "Adresa URL úložiště", "label-target": "Cíl", "label-title": "Název", "option-entire-instance": "Celá instance", - "option-github": "GitHub", - "option-local": "Místní", "option-managed-folder": "Spravovaná složka", - "placeholder-branch": "hlavní", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Moje konfigurace", - "placeholder-select-repository-type": "Vyberte typ úložiště" + "placeholder-my-config": "Moje konfigurace" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Nakonfigurujte webhooky a získejte okamžité aktualizace v Grafaně ihned po potvrzení změn. Před zveřejněním zkontrolujte a schvalte změny pomocí pull requestů.", @@ -10581,27 +11113,8 @@ }, "connect-repository-button": { "configure": "Konfigurovat", - "configure-file": "Konfigurovat zajištění souborů", - "configure-git-sync": "Konfigurovat synchronizaci Git", "repository-limit-info-alert": "Bylo dosaženo limitu úložiště ({{count}})" }, - "connect-step": { - "description-branch": "Větev, která bude použita pro úložiště GitHub", - "description-github-path": "Toto je cesta k dílčímu adresáři ve vašem úložišti GitHub, kde budou uloženy a zajištěny nástěnky", - "description-paste-your-git-hub-personal-access-token": "Vložte svůj osobní přístupový token GitHub", - "description-repository-url": "Vložte adresu URL svého úložiště GitHub", - "error-field-required": "Toto pole je povinné.", - "error-invalid-github-url": "Zadejte platnou adresu URL úložiště GitHub", - "label-access-token": "Přístupový token GitHub", - "label-branch": "Název pobočky", - "label-local-path": "Místní cesta", - "label-path": "Cesta k dílčímu adresáři v úložišti", - "label-repository-url": "Adresa URL úložiště GitHub", - "placeholder-branch": "hlavní", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Odstranit", "confirm-delete-repository": "Opravdu chcete odstranit konfiguraci úložiště?", @@ -10633,17 +11146,17 @@ "title-instant-updates-requests-webhooks": "Okamžité aktualizace a pull requesty pomocí webhooků.", "title-visual-previews-in-pull-requests": "Vizuální náhledy v pull requestech s vykreslením obrázků" }, + "error-title-default": "", "expanded-row": { "job-specification": "Specifikace úlohy", - "summary": "Shrnutí", - "title-error": "Chyba" + "summary": "Shrnutí" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Nastavit povinné přepínače funkcí" }, - "manage-dashboards-provision-updates-automatically": "Spravuje nástěnky jako kód v GitHubu a automaticky zajišťujte aktualizace", - "manage-your-dashboards-with-remote-provisioning": "Začněte s GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Ukládejte nástěnky do úložiště s pevně nastavenou verzí pro lepší organizaci a sledování historie" }, "file-history-page": { @@ -10664,18 +11177,16 @@ }, "finish-step": { "description-enable-previews": "Přidá náhled obrázku změn nástěnky v pull requestech. Obrázky nástěnek Grafana budou sdíleny ve vašem úložišti Git a uvidí je každý, kdo má přístup k úložišti.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Vyžaduje vykreslení obrázků. <2>Nastavit vykreslování obrázků", - "description-often-shall-instance-updates-git-hub": "Jak často bude instance stahovat aktualizace z GitHubu?", - "description-pr-enable-description": "Umožňuje uživatelům zvolit, zda se má při ukládání změn otevřít pull request. Pokud úložiště neumožňuje přímé změny hlavní větve, může být stále vyžadován pull request.", + "description-preview-requirements": "", "description-read-only": "Zdroje nelze upravovat přes Grafanu.", - "description-webhooks-enable": "Umožňuje uživatelům zvolit, zda se má při ukládání změn otevřít pull request. Pokud úložiště neumožňuje přímé změny hlavní větve, může být stále vyžadován pull request.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Povolit náhledy nástěnek v pull requestech", - "label-pr-workflow": "Povolit možnost pull request při ukládání", + "label-generate-dashboard-previews": "", "label-read-only": "Jen pro čtení", - "label-update-instance-interval-seconds": "Interval aktualizace instance (sekundy)", - "placeholder": "60", - "text-setup-later": "Toto můžete kdykoli nastavit později", - "title-enhance-github": "Vylepšete své zkušenosti s GitHubem" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Vašemu dotazu neodpovídají žádné výsledky", @@ -10706,6 +11217,57 @@ "header": "Zajišťování", "subtitle-provisioning-feature": "Zobrazujte a spravujte vazby zajištění" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Nenalezeno" }, @@ -10732,9 +11294,17 @@ "starting": "Spouštění…", "status": { "title-error-running-job": "Chyba při spouštění úlohy", - "title-job-completed-successfully": "Úloha byla úspěšně dokončena" + "title-warning-running-job": "" }, - "summary": "Shrnutí" + "summary": "Shrnutí", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10760,7 +11330,7 @@ }, "repository-actions": { "settings": "Nastavení", - "source-code": "Zdrojový kód" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10825,6 +11395,24 @@ "title-legacy-storage": "Starší verze úložiště", "title-queued-for-deletion": "Zařazeno do fronty k odstranění" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Základ", "dashboard-preview": "Náhled nástěnky", @@ -10854,6 +11442,11 @@ "label-current-step": "Aktuální krok", "label-pending-step": "Nevyřízený krok" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Upravit konfiguraci", "button-edit": "Upravit", @@ -10864,9 +11457,6 @@ "tooltip-unhealthy-repository": "Nelze stáhnout nezdravé úložiště" }, "synchronize-step": { - "error-no-job-id": "Nepodařilo se spustit úlohu", - "error-no-repository-name": "Nebyl zadán žádný název úložiště", - "error-starting-job": "Chyba při spouštění úlohy", "synchronization-description": "Zahrnout předložení pro každou historickou hodnotu", "synchronization-options": "Možnosti synchronizace" }, @@ -10875,6 +11465,7 @@ "go-to": "Přejít na", "make-sure": "Nezapomeňte zahrnout tato oprávnění" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Během tohoto procesu nebude možné vytvářet, upravovat ani odstraňovat zdroje. V posledním kroku zmizí.", "alert-point-2": "Po zajištění se zdroje znovu zobrazí a budou spravovány prostřednictvím externího úložiště.", @@ -11954,7 +12545,6 @@ }, "label-apply-to-entire-row": "Použít na celý řádek", "label-background-display-mode": "Režim zobrazení pozadí", - "label-wrap-text": "Zalomení textu", "wrap-text": "Zalomení textu" }, "column-alignment-options": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index d93a1b78971..72b771fde4e 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -119,10 +119,10 @@ "users-heading": "Benutzer in der Organisation" }, "enterprise-auth-features-card": { - "dismiss": "", - "heading": "", - "learn-more-link": "", - "text": "" + "dismiss": "Schließen", + "heading": "Enterprise-Authentifizierung", + "learn-more-link": "Mehr erfahren", + "text": "Verwalten Sie Nutzer, Teams und Berechtigungen automatisch mit <1>SAML, <3>SCIM, <6>LDAP und <8>RBAC – verfügbar in Grafana Cloud und Grafana Enterprise." }, "feature-listing": { "title-auditing": "Auditing", @@ -374,8 +374,8 @@ "am-active-timing-select-label-active-timings": "Aktive Zeitvorgaben" }, "ad": { - "close": "", - "learn-more": "" + "close": "Schließen", + "learn-more": "Mehr erfahren" }, "add-button": { "add-more": "Weitere hinzufügen" @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -404,12 +405,12 @@ "missing-series-resolve": "Fehlende Reihenbewertungen zu beheben", "no-annotations": "Keine Anmerkungen", "notification-configuration": { - "active-timings": "", + "active-timings": "Aktive Zeitintervalle", "contact-point": "Kontaktpunkt", "group-by": "Gruppiert nach", - "group-title": "", + "group-title": "Benachrichtigungskonfiguration", "mute-timings": "Stummschaltung", - "timing-options": "" + "timing-options": "Timings" }, "pending-period": "Wartezeit", "rule": "Regel", @@ -963,7 +964,7 @@ }, "contactPoints": { "validation": { - "notFound": "" + "notFound": "Kontaktpunkt „{{contactPoint}}“ konnte nicht gefunden werden" } }, "continue-matching-indicator": { @@ -972,7 +973,7 @@ "copy-to-clipboard": "„{{label}}“ in die Zwischenablage kopieren", "create-metadata": { "label": { - "contact-point": "", + "contact-point": "Benachrichtigungen werden übermittelt an", "dashboard": "Dashboard", "dashboard-and-panel": "Dashboard und Panel", "evaluation-interval": "Evaluierungsintervall", @@ -1120,7 +1121,7 @@ "content-show-all-errors": "Alle Fehler anzeigen" }, "errors": { - "failedWith": "" + "failedWith": "{{-config}} fehlgeschlagen mit {{status}}: {{-message}}" }, "evaluate-every-validation-options": { "message": { @@ -1551,7 +1552,8 @@ "title": "Evaluierungsgruppe bearbeiten" }, "group-loader": { - "group-load-failed": "Die Regeln der Gruppe {{ groupName }} in {{ namespaceName }} konnten nicht geladen werden" + "group-load-failed": "Die Regeln der Gruppe {{ groupName }} in {{ namespaceName }} konnten nicht geladen werden", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Die Gruppe wird gelöscht" @@ -1568,20 +1570,20 @@ } }, "home": { - "irm-card-description": "", - "irm-card-item-1": "", - "irm-card-item-2": "", - "irm-card-item-3": "", - "irm-card-item-4": "", - "irm-card-title": "", + "irm-card-description": "Vereinheitlichen Sie die Bereitschaft, Alarmierung und Vorfallreaktion mit Grafana Cloud IRM.", + "irm-card-item-1": "Verwalten Sie Bereitschaftspläne mit Ihrem Kalender oder Terraform.", + "irm-card-item-2": "Reagieren Sie auf Vorfälle über Web, App, Slack oder andere Kanäle.", + "irm-card-item-3": "Identifizieren Sie die Ursachen mit dem KI-gestützten Grafana SIFT.", + "irm-card-item-4": "Analysieren Sie vergangene Vorfälle, um die Reaktion und Resilienz zu verbessern.", + "irm-card-title": "Vorfallreaktion und Vorfallmanagement", "label-get-started": "Erste Schritte", "label-insights": "Insights", - "synthetic-monitoring-card-description": "", - "synthetic-monitoring-card-item-1": "", - "synthetic-monitoring-card-item-2": "", - "synthetic-monitoring-card-item-3": "", - "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", + "synthetic-monitoring-card-description": "Überwachen Sie kritische Nutzerflows, Websites und APIs extern von globalen Standorten aus.", + "synthetic-monitoring-card-item-1": "Simulieren Sie End-to-End-Nutzer-Journeys mit Browser-Prüfungen.", + "synthetic-monitoring-card-item-2": "Führen Sie auf jeder Netzwerkebene Ping-, DNS-, HTTP/S- und TCP-Prüfungen durch.", + "synthetic-monitoring-card-item-3": "Nutzen Sie mehr als 20 globale Tests oder private Tests hinter Ihrer Firewall.", + "synthetic-monitoring-card-item-4": "Verfolgen Sie SLOs mit integrierten Warnungen im Stil von Prometheus – direkt über die Benutzeroberfläche.", + "synthetic-monitoring-card-title": "Synthetisches Monitoring", "title-alerting": "Meldungen" }, "import-to-gma": { @@ -1596,7 +1598,7 @@ "no-rules-body-yaml": "Es gibt keine Regeln, die importiert werden könnten. Bitte wählen Sie eine andere YAML-Datei.", "no-rules-title": "Keine Regeln zum Importieren", "not-using-rules-managed-by-integrations-or-plugins": { - "text": "", + "text": "Regeln, die von Integrationen oder Plugins verwaltet werden, sollten nicht zu Regeln importiert werden, die von Grafana verwaltet werden.", "title": "Informationen" }, "plugin-rules-warning": { @@ -1976,7 +1978,7 @@ }, "notification-policies-filter": { "label-search-by-contact-point": "Suche nach Kontaktpunkt", - "placeholder-search-by-contact-point": "", + "placeholder-search-by-contact-point": "Wählen Sie einen Kontaktpunkt", "search-query-input-placeholder-search": "Suche" }, "notification-policies-list": { @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Konfigurieren", - "draft-new-rule": "Neue Regel entwerfen", "ds-error": { "title": "Regeln für diese Datenquelle können nicht geladen werden" }, @@ -2487,11 +2488,12 @@ "new-badge": "Neu!", "text": "Importieren zu von Grafana verwalteten Regeln" }, - "loading-more-groups": "", + "loading-more-groups": "Weitere Gruppen werden geladen …", "more": "Mehr", "new-alert-rule": "Neue Warnregel", "new-datasource-recording-rule": "Neue Datenquelle-Aufnahmeregel", "new-grafana-recording-rule": "Neue Grafana-Aufnahmeregel", + "new-rule-for-export": "", "pagination": { "next-page": "Mehr anzeigen …" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Vorschau aktualisieren", + "empty-state": "", "label-preview": "Vorschau", "refresh": "Aktualisieren" }, @@ -3038,51 +3041,51 @@ } }, "alertlist": { - "category-alert-state-filter": "", - "category-filter": "", - "category-options": "", - "descriptino-alerts-linked-to-dashboard": "", - "description-alert-instance-label": "", - "description-alert-name": "", - "description-datasource": "", - "description-folder": "", - "description-group-by": "", - "description-group-mode": "", - "description-max-items": "", - "description-show-inactive-alerts": "", - "description-sort-order": "", - "description-view-mode": "", + "category-alert-state-filter": "Warnzustandsfilter", + "category-filter": "Filter", + "category-options": "Optionen", + "descriptino-alerts-linked-to-dashboard": "Nur mit diesem Dashboard verknüpfte Warnungen anzeigen", + "description-alert-instance-label": "Filtern Sie Warnungsinstanzen mithilfe von Label-Abfragen wie: {severity=\"critical\", instance=~\"cluster-us-.+\"}", + "description-alert-name": "Filtern Sie nach Warnungen, die diesen Text enthalten", + "description-datasource": "Filtern Sie nach der Warnungsquelle", + "description-folder": "Filtern Sie nach Warnungen im ausgewählten Ordner (nur für Warnregeln, die von Grafana verwaltet werden)", + "description-group-by": "Filtern Sie Warnungen mithilfe von Label-Abfragen", + "description-group-mode": "Wie Warnungsinstanzen gruppiert werden sollten", + "description-max-items": "Maximale Anzahl der anzuzeigenden Warnungen", + "description-show-inactive-alerts": "Warnregeln mit 0 (null) Instanzen einbeziehen. Da diese Regeln keine Instanzen haben, bleiben sie ausgeblendet, wenn der Label-Filter für Warnungsinstanzen konfiguriert ist.", + "description-sort-order": "Sortierreihenfolge von Warnungen und Warnungsinstanzen", + "description-view-mode": "Zwischen Listenansicht und Statistikansicht wechseln", "group-by": { "aria-label-group-by-label-keys": "Nach Label-Keys gruppieren", "placeholder-group-by": "Gruppieren nach" }, "group-mode-options": { - "label-custom-grouping": "", - "label-default-grouping": "" + "label-custom-grouping": "Benutzerdefinierte Gruppierung", + "label-default-grouping": "Standardgruppierung" }, - "name-alert-instance-label": "", - "name-alert-name": "", - "name-alerts-linked-to-dashboard": "", - "name-datasource": "", - "name-error": "", - "name-firing": "", - "name-folder": "", - "name-group-by": "", - "name-group-mode": "", - "name-max-items": "", - "name-no-data": "", - "name-normal": "", - "name-pending": "", - "name-recovering": "", - "name-show-inactive-alerts": "", - "name-sort-order": "", - "name-view-mode": "", + "name-alert-instance-label": "Warnungsinstanz-Label", + "name-alert-name": "Name der Warnung", + "name-alerts-linked-to-dashboard": "Mit diesem Dashboard verknüpfte Warnungen", + "name-datasource": "Datenquelle", + "name-error": "Fehler", + "name-firing": "Warnung/Auslösung", + "name-folder": "Ordner", + "name-group-by": "Gruppieren nach", + "name-group-mode": "Gruppenmodus", + "name-max-items": "Max. Elemente", + "name-no-data": "Keine Daten", + "name-normal": "Normal", + "name-pending": "Ausstehend", + "name-recovering": "Wird wiederhergestellt", + "name-show-inactive-alerts": "Warnungen mit 0 Instanzen anzeigen", + "name-sort-order": "Sortierreihenfolge", + "name-view-mode": "Ansichtsmodus", "sort-order-options": { - "label-alphabetical-asc": "", - "label-alphabetical-desc": "", - "label-importance": "", - "label-time-asc": "", - "label-time-desc": "" + "label-alphabetical-asc": "Alphabetisch (aufsteigend)", + "label-alphabetical-desc": "Alphabetisch (absteigend)", + "label-importance": "Relevanz", + "label-time-asc": "Zeit (aufsteigend)", + "label-time-desc": "Zeit (absteigend)" }, "ungrouped-mode-view": { "active-for": "<1>{{duration}} lang", @@ -3098,8 +3101,8 @@ "title-permission-required": "Berechtigung erforderlich" }, "view-mode-options": { - "label-list": "", - "label-stat": "" + "label-list": "Liste", + "label-stat": "Stat." } }, "annolist": { @@ -3112,31 +3115,31 @@ "annotation-list-item": { "tooltip-created-by": "Erstellt von:<1> {{email}}" }, - "category-annotation-query": "", - "category-display": "", - "category-link-behaviour": "", - "description-tags": "", + "category-annotation-query": "Annotationsabfrage", + "category-display": "Display", + "category-link-behaviour": "Link-Verhalten", + "description-tags": "Annotations-Tags abgleichen", "link-target-options": { - "label-dashboard": "", - "label-panel": "" + "label-dashboard": "Dashboard", + "label-panel": "Panel" }, - "name-limit": "", - "name-link-target": "", - "name-query-filter": "", - "name-show-tags": "", - "name-show-time": "", - "name-show-user": "", - "name-tags": "", - "name-time-after": "", - "name-time-before": "", - "name-time-range": "", + "name-limit": "Grenze", + "name-link-target": "Linkziel", + "name-query-filter": "Abfragefilter", + "name-show-tags": "Tags anzeigen", + "name-show-time": "Zeit anzeigen", + "name-show-user": "Nutzer anzeigen", + "name-tags": "Tags", + "name-time-after": "Zeitraum danach", + "name-time-before": "Zeitraum davor", + "name-time-range": "Zeitbereich", "query-filter-options": { - "label-all-dashboards": "", - "label-this-dashboard": "" + "label-all-dashboards": "Alle Dashboards", + "label-this-dashboard": "Dieses Dashboard" }, "time-range-options": { - "label-none": "", - "label-this-dashboard": "" + "label-none": "Keine", + "label-this-dashboard": "Dieses Dashboard" } }, "annotations": { @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "ID-Token-Attributname", "login-attribute-path-description": "JMESPath-Ausdruck, der für die Nutzer-Login-Suche aus dem Nutzer-ID-Token verwendet wird.", "login-attribute-path-label": "Anmeldeattributpfad", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "Die Client-ID zur verwalteten Identität der Verbundidentitäts-Anmeldedaten Ihrer OAuth2-App.", "managed-identity-client-id-label": "Client-ID der FIC-verwalteten Identität", "name-attribute-path-description": "JMESPath-Ausdruck, der für die Nutzername-Suche aus dem Nutzer-ID-Token verwendet wird. \nDieser Name wird als Anzeigename des Nutzers verwendet.", @@ -3326,8 +3334,8 @@ } }, "provider-card": { - "saml-learn-more": "", - "scim-learn-more": "", + "saml-learn-more": "Einmalanmeldung (SSO) mit SAML.", + "scim-learn-more": " Synchronisieren Sie Nutzer und Teams mit SCIM.", "text-badge-enabled": "Aktiviert", "text-badge-not-enabled": "Nicht aktiviert" }, @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Aktiviert", "text-badge-not-enabled": "Nicht aktiviert" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Der Endpunkt .well-known/openid-configuration für Ihren IdP", "title-open-id-connect-discovery-url": "OpenID-Connect-Discovery-URL" @@ -3364,99 +3375,99 @@ }, "bar-chart": { "warn": { - "missing-numeric": "", - "missing-series": "" + "missing-numeric": "Keine numerischen Felder gefunden", + "missing-series": "Balkendiagramme erfordern ein String- oder Zeitfeld" } }, "barchart": { "config": { - "category-thresholds": "", - "description-color-by-field": "", - "description-x-label-max-length": "", - "name-bar-radius": "", - "name-bar-width": "", - "name-color-by-field": "", - "name-fill-opacity": "", - "name-full-highlight": "", - "name-gradient-mode": "", - "name-group-width": "", - "name-line-width": "", - "name-orientation": "", - "name-rotate-x-labels": "", - "name-show-thresholds": "", - "name-show-values": "", - "name-stacking": "", - "name-transform": "", - "name-x-axis": "", - "name-x-label-max-length": "", - "name-x-label-min-spacing": "", + "category-thresholds": "Schwellenwerte", + "description-color-by-field": "Verwenden Sie den Farbwert für ein gleichrangiges Feld, um jeden Balkenwert zu färben.", + "description-x-label-max-length": "X-Achsen-Labels werden auf die angegebene Länge gekürzt", + "name-bar-radius": "Balkenradius", + "name-bar-width": "Balkenbreite", + "name-color-by-field": "Farbe nach Feld", + "name-fill-opacity": "Fülldeckkraft", + "name-full-highlight": "Beim Hovern vollständigen Bereich hervorheben", + "name-gradient-mode": "Farbverlaufsmodus", + "name-group-width": "Gruppenbreite", + "name-line-width": "Linienbreite", + "name-orientation": "Ausrichtung", + "name-rotate-x-labels": "X-Achsen-Teilstrich-Labels drehen", + "name-show-thresholds": "Schwellenwerte anzeigen", + "name-show-values": "Werte anzeigen", + "name-stacking": "Stapeln", + "name-transform": "Transformieren", + "name-x-axis": "X-Achse", + "name-x-label-max-length": "Maximale Länge der X-Achsen-Teilstrich-Labels", + "name-x-label-min-spacing": "Mindestabstand der X-Achsen-Labels", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-line-vertical": "" + "label-auto": "Auto", + "label-horizontal": "Horizontal", + "label-line-vertical": "Vertikal" }, - "placeholder-x-axis": "", - "placeholder-x-label-max-length": "", + "placeholder-x-axis": "Erstes String- oder Zeitfeld", + "placeholder-x-label-max-length": "Keine", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Immer", + "label-auto": "Auto", + "label-never": "Nie" }, "transform-options": { - "description-constant": "", - "description-negative-y": "", - "label-constant": "", - "label-negative-y": "" + "description-constant": "Der erste Wert wird als konstante Linie angezeigt", + "description-negative-y": "Ergebnisse auf der Y-Achse auf negative Werte umkehren", + "label-constant": "Konstant", + "label-negative-y": "Negativ Y" } }, "tick-spacing-editor": { "content-require-space-from-the-right-side": "Leerzeichen von der rechten Seite erforderlich", "gaps-options": { - "description-large": "", - "description-medium": "", - "description-none": "", - "description-small": "", - "label-large": "", - "label-medium": "", - "label-none": "", - "label-small": "" + "description-large": "Abstand von {{spacing}} erforderlich", + "description-medium": "Abstand von {{spacing}} erforderlich", + "description-none": "Alle Teilstriche anzeigen", + "description-small": "Abstand von {{spacing}} erforderlich", + "label-large": "Groß", + "label-medium": "Mittel", + "label-none": "Keine", + "label-small": "Klein" }, "label-rtl": "RTL" } }, "bargauge": { "bar-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Auto", + "label-manual": "Manuell" }, - "category-bar-gauge": "", - "description-max-height": "", - "description-min-height": "", - "description-min-width": "", - "description-show-unfilled-area": "", + "category-bar-gauge": "Balkenanzeige", + "description-max-height": "Maximale Zeilenhöhe (horizontale Ausrichtung)", + "description-min-height": "Minimale Zeilenhöhe (horizontale Ausrichtung)", + "description-min-width": "Minimale Spaltenbreite (vertikale Ausrichtung)", + "description-show-unfilled-area": "Wenn diese Option aktiviert ist, wird der ungefüllte Bereich grau dargestellt", "display-mode-options": { - "label-basic": "", - "label-gradient": "", - "label-retro": "" + "label-basic": "Einfach", + "label-gradient": "Farbverlauf", + "label-retro": "Retro-LCD" }, - "name-bar-size": "", - "name-display-mode": "", - "name-max-height": "", - "name-min-height": "", - "name-min-width": "", - "name-name-placement": "", + "name-bar-size": "Balkengröße", + "name-display-mode": "Anzeigemodus", + "name-max-height": "Max. Höhe", + "name-min-height": "Min. Höhe", + "name-min-width": "Min. Breite", + "name-name-placement": "Namensplatzierung", "name-placement-options": { - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-top": "" + "label-auto": "Auto", + "label-hidden": "Ausgeblendet", + "label-left": "Links", + "label-top": "Oben" }, - "name-show-unfilled-area": "", - "name-value-display": "", + "name-show-unfilled-area": "Ungefüllten Bereich anzeigen", + "name-value-display": "Wertanzeige", "value-display-options": { - "label-hidden": "", - "label-text-color": "", - "label-value-color": "" + "label-hidden": "Ausgeblendet", + "label-text-color": "Textfarbe", + "label-value-color": "Wertfarbe" } }, "bookmarks-page": { @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Abbrechen", "cannot-move-folders": "Ordner können nicht verschoben werden", "confirmation-text": "Löschen", @@ -3481,7 +3491,7 @@ "delete-modal-restore-dashboards-text": "Durch diese Aktion werden die ausgewählten Ordner sofort gelöscht, aber die ausgewählten Dashboards werden innerhalb von 30 Tagen zur Löschung markiert. Der Administrator Ihrer Organisation kann die Dashboards jederzeit vor Ablauf der 30 Tage wiederherstellen. Ordner können nicht wiederhergestellt werden.", "delete-modal-text": "Diese Aktion wird folgenden Inhalt löschen:", "delete-modal-title": "Löschen", - "delete-provisioned-folder": "", + "delete-provisioned-folder": "Bereitgestellten Ordner löschen", "deleting": "Löschen...", "manage-permissions-button": "Berechtigungen verwalten", "move-button": "Verschieben", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Dieser Ordner ist leer" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} Alarmierungsregel", "alertRule_other": "{{count}} Alarmierungsregeln", @@ -3525,12 +3553,12 @@ "tags-column": "Tags" }, "delete-provisioned-folder-form": { - "alert-folder-deleted-successfully": "", - "api-error": "", - "button-cancel": "", - "button-delete": "", - "button-deleting": "", - "delete-warning": "" + "alert-folder-deleted-successfully": "Ordner erfolgreich gelöscht", + "api-error": "Ordner konnte nicht gelöscht werden", + "button-cancel": "Abbrechen", + "button-delete": "Löschen", + "button-deleting": "Wird gelöscht …", + "delete-warning": "Dadurch werden dieser und alle untergeordneten Ordner gelöscht. Insgesamt betrifft dies:" }, "descendant-count": { "title-unable-to-retrieve-descendant-information": "Nachfolgerinformationen können nicht abgerufen werden" @@ -3554,6 +3582,7 @@ "empty-message": "Keine Ordner gefunden", "error-title": "Fehler beim Laden der Ordner", "non-folder-item": "{{itemKind}} {{itemUID}} nicht im Ordner enthalten", + "root-title": "", "search-placeholder": "Ordner suchen", "unknown-error": "Unbekannter Fehler" }, @@ -3589,52 +3618,52 @@ "text": "Keine Ergebnisse für deine Abfrage gefunden" }, "restore": { - "success": "" + "success": "Dashboard {{name}} wiederhergestellt" }, "text-this-repository-is-read-only": "Wenn Sie direkten Zugriff auf das Ziel haben, kopieren Sie den JSON und fügen Sie ihn dort ein.", "trash-state-manager": { "label": { - "alphabetically-az": "", - "alphabetically-za": "" + "alphabetically-az": "Alphabetisch (A–Z)", + "alphabetically-za": "Alphabetisch (Z–A)" } } }, "candlestick": { "additional-fields-options": { - "label-ignore": "", - "label-include": "" + "label-ignore": "Ignorieren", + "label-include": "Einschließen" }, "candle-style-options": { - "label-candles": "", - "label-ohlc-bars": "" + "label-candles": "Kerzen", + "label-ohlc-bars": "OHLC-Balken" }, - "category-candlestick": "", + "category-candlestick": "Candlestick", "color-strategy-options": { - "label-since-open": "", - "label-since-prior-close": "" + "label-since-open": "Seit dem Öffnen", + "label-since-prior-close": "Seit dem letzten Schließen" }, - "description-additional-fields": "", - "description-close": "", - "description-high": "", - "description-low": "", - "description-open": "", - "description-volume": "", + "description-additional-fields": "Verwenden Sie standardmäßige Zeitreihen-Optionen, um oben nicht zugeordnete Felder zu konfigurieren", + "description-close": "Wert am Ende des Zeitraums", + "description-high": "Maximalwert innerhalb des Zeitraums", + "description-low": "Minimalwert innerhalb des Zeitraums", + "description-open": "Wert zu Beginn des Zeitraums", + "description-volume": "Probenzahl innerhalb des Zeitraums", "mode-options": { - "label-both": "", - "label-candles": "", - "label-volume": "" + "label-both": "Beides", + "label-candles": "Kerzen", + "label-volume": "Volumen" }, - "name-additional-fields": "", - "name-candle-style": "", - "name-close": "", - "name-color-strategy": "", - "name-down-color": "", - "name-high": "", - "name-low": "", - "name-mode": "", - "name-open": "", - "name-up-color": "", - "name-volume": "" + "name-additional-fields": "Zusätzliche Felder", + "name-candle-style": "Kerzenstil", + "name-close": "Schließen", + "name-color-strategy": "Farbstrategie", + "name-down-color": "Farbe unten", + "name-high": "Hoch", + "name-low": "Niedrig", + "name-mode": "Modus", + "name-open": "Öffnen", + "name-up-color": "Farbe oben", + "name-volume": "Volumen" }, "canvas": { "apieditor": { @@ -3649,18 +3678,18 @@ } }, "button-item": { - "category-button": "", + "category-button": "Schaltfläche", "label": { "center": "Mitte", "left": "Links", "right": "Rechts" }, - "name-align-text": "", - "name-api": "", - "name-style": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", + "name-align-text": "Text ausrichten", + "name-api": "API", + "name-style": "Stil", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", "placeholder": { "auto": "Auto" } @@ -3687,20 +3716,20 @@ } } }, - "category-arrow-direction": "", - "category-background": "", - "category-border": "", - "category-canvas": "", - "category-color": "", - "category-data-links": "", - "category-line-style": "", - "category-metric-value": "", - "category-radius": "", - "category-selected-connection": "", - "category-selected-element": "", - "category-size": "", + "category-arrow-direction": "Pfeilrichtung", + "category-background": "Hintergrund", + "category-border": "Rahmen", + "category-canvas": "Leinwand", + "category-color": "Farbe", + "category-data-links": "Datenlinks und Aktionen", + "category-line-style": "Linienstil", + "category-metric-value": "Metrikwert", + "category-radius": "Radius", + "category-selected-connection": "Ausgewählte Verbindung", + "category-selected-element": "Ausgewähltes Element ({{element}})", + "category-size": "Größe", "cloud-item": { - "category-cloud": "", + "category-cloud": "Cloud", "label": { "bottom": "Unten", "center": "Mitte", @@ -3709,48 +3738,48 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } }, - "description-experimental-types": "", - "description-infinite-panning": "", - "description-inline-editing": "", - "description-pan-zoom": "", + "description-experimental-types": "Auswahl experimenteller Elementtypen aktivieren", + "description-infinite-panning": "Unbegrenztes Schwenken aktivieren – nützlich für umfangreiche Leinwände. Warnung: Dies ist eine experimentelle Funktion und funktioniert momentan nur mit Elementen gut, die oben/links eingeschränkt sind", + "description-inline-editing": "Direkte Bearbeitung des Panels aktivieren", + "description-pan-zoom": "Schwenken und Zoomen aktivieren", "direction-options": { - "label-both": "", - "label-forward": "", - "label-none": "", - "label-reverse": "" + "label-both": "Beides", + "label-forward": "Vorwärts", + "label-none": "Keine", + "label-reverse": "Rückwärts" }, "drone-front-item": { - "category-drone-front": "", - "name-roll-angle": "" + "category-drone-front": "Drohne vorne", + "name-roll-angle": "Rollwinkel" }, "drone-side-item": { - "category-drone-side": "", - "name-pitch-angle": "" + "category-drone-side": "Drohne Seite", + "name-pitch-angle": "Steigungswinkel" }, "drone-top-item": { - "category-drone-top": "", - "name-back-left-rotor-rpm": "", - "name-back-right-rotor-rpm": "", - "name-front-left-rotor-rpm": "", - "name-front-right-rotor-rpm": "", - "name-yaw-angle": "" + "category-drone-top": "Drohne oben", + "name-back-left-rotor-rpm": "Drehzahl des linken hinteren Rotors", + "name-back-right-rotor-rpm": "Drehzahl des rechten hinteren Rotors", + "name-front-left-rotor-rpm": "Drehzahl des linken vorderen Rotors", + "name-front-right-rotor-rpm": "Drehzahl des rechten vorderen Rotors", + "name-yaw-angle": "Gierwinkel" }, "element-editor": { - "category-layout": "", - "description-unsupported": "", - "name-quick-placement": "" + "category-layout": "Layout", + "description-unsupported": "Der ausgewählte Elementtyp wird von den aktuellen Einstellungen nicht unterstützt. Bitte aktivieren Sie erweiterte Elementtypen.", + "name-quick-placement": "Schnelle Platzierung" }, "ellipse-item": { - "category-ellipse": "", + "category-ellipse": "Ellipse", "label": { "bottom": "Unten", "center": "Mitte", @@ -3759,50 +3788,50 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } }, "icon-item": { - "category-icon": "", - "name-fill-color": "", - "name-svg-path": "" + "category-icon": "Symbol", + "name-fill-color": "Füllfarbe", + "name-svg-path": "SVG Path" }, "inline-edit": { "canvas-inline-editor": "Canvas-Inline-Editor", "tooltip-close-inline-editor": "Inline-Editor schließen" }, "inline-edit-body": { - "category-selected-element": "", + "category-selected-element": "Ausgewähltes Element ({{element}})", "label-add-item": "Element hinzufügen", "please-select-an-element": "Bitte wählen Sie ein Element" }, - "label-actions": "", - "label-color": "", - "label-direction": "", - "label-image": "", - "label-image-size": "", - "label-line-style": "", - "label-links": "", - "label-radius": "", - "label-size": "", - "label-width": "", + "label-actions": "Aktionen", + "label-color": "Farbe", + "label-direction": "Richtung", + "label-image": "Bild", + "label-image-size": "Bildgröße", + "label-line-style": "Linienstil", + "label-links": "Links", + "label-radius": "Radius", + "label-size": "Größe", + "label-width": "Breite", "layer-editor": { - "category-layer": "", - "category-layout": "", - "name-constraints": "", - "name-elements": "" + "category-layer": "Layer", + "category-layout": "Layout", + "name-constraints": "Beschränkungen", + "name-elements": "Elemente" }, "line-style-editor": { "label-animate": "Animieren" }, "line-style-options": { - "label-solid": "" + "label-solid": "Durchgezogen" }, "metric-value-item": { "label": { @@ -3817,15 +3846,15 @@ "auto": "Auto" } }, - "name-align-text": "", - "name-color": "", - "name-experimental-types": "", - "name-infinite-panning": "", - "name-inline-editing": "", - "name-pan-zoom": "", - "name-text": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-color": "Textfarbe", + "name-experimental-types": "Experimentelle Elementtypen", + "name-infinite-panning": "Unbegrenztes Schwenken", + "name-inline-editing": "Inline-Bearbeitung", + "name-pan-zoom": "Schwenken und Zoomen", + "name-text": "Text", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "not-found-display": { "not-found": "<0>Nicht gefunden: " }, @@ -3838,7 +3867,7 @@ "zoom-scroll-wheel": "Zoomen: Scrollrad" }, "parallelogram-item": { - "category-parallelogram": "", + "category-parallelogram": "Parallelogramm", "label": { "bottom": "Unten", "center": "Mitte", @@ -3847,11 +3876,11 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } @@ -3864,21 +3893,21 @@ }, "placement-editor": { "horizontal-options": { - "label-center": "", - "label-left": "", - "label-left-and-right": "", - "label-right": "", - "label-scale": "" + "label-center": "Mitte", + "label-left": "Links", + "label-left-and-right": "Links und rechts", + "label-right": "Rechts", + "label-scale": "Skalieren" }, "label-constraints": "Beschränkungen", "label-position": "Position", "loading": "Wird geladen ...", "vertical-options": { - "label-bottom": "", - "label-center": "", - "label-scale": "", - "label-top": "", - "label-top-and-bottom": "" + "label-bottom": "Unten", + "label-center": "Mitte", + "label-scale": "Skalieren", + "label-top": "Oben", + "label-top-and-bottom": "Oben und unten" } }, "quick-positioning": { @@ -3890,7 +3919,7 @@ "tooltip-align-vertical-centers": "Vertikale mittig ausrichten" }, "rectangle-item": { - "category-rectangle": "", + "category-rectangle": "Rechteck", "label": { "bottom": "Unten", "center": "Mitte", @@ -3899,33 +3928,33 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } }, "server-item": { - "category-server": "", - "name-blink-rate": "", - "name-bulb-color": "", - "name-status-color": "", - "name-type": "", + "category-server": "Server", + "name-blink-rate": "Blinkfrequenz [hz] (0 = aus)", + "name-bulb-color": "Birnen-Farbe", + "name-status-color": "Status-Farbe", + "name-type": "Typ", "type-options": { - "label-database": "", - "label-single": "", - "label-stack": "", - "label-terminal": "" + "label-database": "Datenbank", + "label-single": "Einzeln", + "label-stack": "Stapel", + "label-terminal": "Terminal" } }, "text-display": { "double-click-to-set": "Klicken Sie doppelt, um Text festzulegen" }, "text-item": { - "category-text": "", + "category-text": "Text", "label": { "bottom": "Unten", "center": "Mitte", @@ -3934,11 +3963,11 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } @@ -3960,7 +3989,7 @@ "tooltip-remove": "Entfernen" }, "triangle-item": { - "category-triangle": "", + "category-triangle": "Dreieck", "label": { "bottom": "Unten", "center": "Mitte", @@ -3969,18 +3998,18 @@ "right": "Rechts", "top": "Oben" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Text ausrichten", + "name-text": "Text", + "name-text-color": "Textfarbe", + "name-text-size": "Textgröße", + "name-vertical-align": "Vertikale Ausrichtung", "placeholder": { "auto": "Auto" } }, "wind-turbine-item": { - "category-wind-turbine": "", - "name-rpm": "" + "category-wind-turbine": "Windturbine", + "name-rpm": "Drehzahl" } }, "carousel": { @@ -3997,8 +4026,8 @@ "close-button": { "tooltip": "Schließen" }, - "cloud-enterprise-feature-badge": "", - "cloud-feature-badge": "", + "cloud-enterprise-feature-badge": "Cloud und Enterprise", + "cloud-feature-badge": "Cloud", "combobox": { "async": { "error": "Beim Laden der Optionen ist ein Fehler aufgetreten." @@ -4528,7 +4557,7 @@ } }, "description": { - "action": "" + "action": "Dashboard-Beschreibung ändern" }, "dynamic-config-value-editor": { "render-label": { @@ -4537,12 +4566,16 @@ }, "edit-actions": { "add": "{{typeName}} hinzufügen", - "move": "", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", + "move": "Verschieben von {{typeName}}", "panel-background": "Panel-Hintergrund ändern", "panel-description": "Panel-Beschreibung ändern", - "panel-max-repeats-per-row": "", + "panel-max-repeats-per-row": "Max. Wiederholungen pro Zeile", "panel-repeat-direction": "Richtung wiederholen", - "panel-repeat-variable": "", + "panel-repeat-variable": "Panel-Wiederholung durch", "panel-title": "Panel-Titel ändern", "paste-panel": "Panel einfügen", "remove": "{{typeName}} entfernen", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "element": "", "local-variable": "Lokale Variable", "multiple-elements": "Mehrere Elemente", "multiple-elements-delete-text": "Sind Sie sicher, dass Sie diese Elemente löschen möchten?", @@ -4676,7 +4710,7 @@ "get-field-override-categories": { "label-add-field-override": "Feldüberschreibung hinzufügen", "label-add-override-property": "Überschreibungseigenschaft hinzufügen", - "override-name": "", + "override-name": "Überschreiben von {{overrideNum}}", "title": { "add-button": "Hinzufügen-Schaltfläche" } @@ -4901,7 +4935,7 @@ "title-matched_other": "Übereinstimmend {{count}}/{{totalCount}} Optionen" }, "outline": { - "repeated-item": "", + "repeated-item": "Wiederholen", "tree-item": { "empty": "(leer)", "no-title": "" @@ -5059,8 +5093,8 @@ "label-changes": "Änderungen", "label-details": "Details", "title": "Dashboard speichern", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Dashboard-Kopie speichern", + "title-provisioned": "Bereitgestelltes Dashboard" }, "save-dashboard-error-proxy": { "body-name-exists": "Ein Dashboard mit demselben Namen im ausgewählten Ordner existiert bereits.<1><2>Möchten Sie dieses Dashboard trotzdem speichern?", @@ -5137,7 +5171,7 @@ } }, "title": { - "action": "" + "action": "Dashboard-Titel ändern" }, "toolbar": { "add": "Hinzufügen", @@ -5572,12 +5606,12 @@ } }, "delete-provisioned-dashboard-form": { - "api-error": "", + "api-error": "Das Dashboard konnte nicht gelöscht werden", "cancel-action": "Abbrechen", "delete-action": "Dashboard löschen", - "delete-read-only-file-message": "", + "delete-read-only-file-message": "Dieses Dashboard kann nicht direkt von Grafana aus gelöscht werden, da das Repository schreibgeschützt ist. Um dieses Dashboard zu löschen, entfernen Sie bitte die Datei aus Ihrem Git-Repository.", "deleting": "Löschen...", - "drawer-title": "", + "drawer-title": "Bereitgestelltes Dashboard löschen", "title-this-repository-is-read-only": "Dieses Repository ist schreibgeschützt" }, "description-label": { @@ -5591,7 +5625,7 @@ } }, "edit-provisioned-dashboard-form": { - "success": "" + "success": "Dashboard-Änderungen erfolgreich gespeichert" }, "email-list": { "aria-label-emailmenu": "E-Mail-Menü umschalten" @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5785,7 +5818,7 @@ } }, "panel-data-alerting-tab": { - "tab-label": "" + "tab-label": "Warnung" }, "panel-data-alerting-tab-rendered": { "alert": { @@ -5796,14 +5829,14 @@ "title-dashboard-not-saved": "Dashboard nicht gespeichert" }, "panel-data-queries-tab": { - "tab-label": "" + "tab-label": "Abfragen" }, "panel-data-queries-tab-rendered": { "add-query": "Abfrage hinzufügen", "expression": "Ausdruck " }, "panel-data-transformations-tab": { - "tab-label": "" + "tab-label": "Transformationen" }, "panel-data-transformations-tab-rendered": { "add-another-transformation": "Weitere Transformation hinzufügen", @@ -5906,8 +5939,8 @@ "label-changes": "Änderungen", "label-details": "Details", "title": "Dashboard speichern", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Dashboard-Kopie speichern", + "title-provisioned": "Bereitgestelltes Dashboard" } }, "save-dashboard-form": { @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Eine andere Person hat dieses Dashboard aktualisiert", "would-still-dashboard": "Möchten Sie dieses Dashboard trotzdem speichern?" }, - "schema-v2-library-panels-export": "Die dynamische Dashboard-Funktionalität ist experimentell und weist keine vollständige Funktionsparität mit dem aktuellen Dashboard-Verhalten auf. Sie basiert auf einem neuen Schemaformat, das keine Bibliotheks-Panels unterstützt. Das bedeutet, dass wir bei einem Export des Dashboards für die Nutzung in einer anderen Instanz keine Bibliotheks-Panels einbeziehen. Wir planen, sie im weiteren Verlauf des <2>Lebenszyklus der Funktion zu unterstützen.", - "schema-v2-library-panels-export-title": "Das Dashboard-Schema V2 unterstützt noch keinen Export von Bibliotheks-Panels für die Verwendung in einer anderen Instanz", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Dashboard irreversibel geändert" }, "save-dashboard-form-common-options": { @@ -6231,18 +6264,18 @@ } }, "dashlist": { - "category-dashboard-list": "", - "name-folder": "", - "name-include-current-template-variables": "", - "name-include-current-time-range": "", - "name-max-items": "", - "name-query": "", - "name-recently-viewed": "", - "name-search": "", - "name-show-folder-names": "", - "name-show-headings": "", - "name-starred": "", - "name-tags": "" + "category-dashboard-list": "Dashboard-Liste", + "name-folder": "Ordner", + "name-include-current-template-variables": "Aktuelle Vorlagenvariablenwerte einbeziehen", + "name-include-current-time-range": "Aktuellen Zeitbereich einbeziehen", + "name-max-items": "Max. Elemente", + "name-query": "Abfrage", + "name-recently-viewed": "Zuletzt angesehen", + "name-search": "Suchen", + "name-show-folder-names": "Ordnernamen anzeigen", + "name-show-headings": "Überschriften anzeigen", + "name-starred": "Mit Stern versehen", + "name-tags": "Tags" }, "data-source-list": { "empty-state": { @@ -6504,15 +6537,15 @@ "dimensions": { "background-size-editor": { "options": { - "label-contain": "", - "label-cover": "", - "label-fill": "", - "label-original": "", - "label-tile": "" + "label-contain": "Contain", + "label-cover": "Cover", + "label-fill": "Fill", + "label-original": "Original", + "label-tile": "Tile" } }, "color-dimension-editor": { - "label-fixed-color": "", + "label-fixed-color": "Feste Farbe", "noOptionsMessage-no-fields-found": "" }, "file-dropzone-custom-children": { @@ -6527,10 +6560,10 @@ "placeholder-search": "Suche" }, "resource-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Ein String-Feld-Ergebnis verwenden", + "description-fixed": "Fester Wert", "label-field": "Feld", - "label-fixed": "", + "label-fixed": "Fest", "label-mappings": "Mappings", "label-source": "Quelle" }, @@ -6547,16 +6580,16 @@ }, "scalar-dimension-editor": { "fixed-value-options": { - "label-fixed-values": "" + "label-fixed-values": "Fester Wert" }, "label-limit": "Limit", "label-value": "Wert", "noOptionsMessage-no-fields-found": "", "scalar-options": { - "description-clamped": "", - "description-mod": "", - "label-clamped": "", - "label-mod": "" + "description-clamped": "Feldwerte verwenden, auf max. und min. begrenzt", + "description-mod": "Feldwerte verwenden, Modulo von max.", + "label-clamped": "Begrenzt", + "label-mod": "Modulo" } }, "scale-dimension-editor": { @@ -6571,10 +6604,10 @@ "noOptionsMessage-no-fields-found": "" }, "text-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Feldwert anzeigen", + "description-fixed": "Fester Wert", "label-field": "Feld", - "label-fixed": "", + "label-fixed": "Fest", "label-source": "Quelle", "label-template": "Vorlage", "label-value": "Wert" @@ -6588,16 +6621,16 @@ "description-percentage-means-thresholds-relative": "Prozent bedeutet Schwellenwerte relativ zu min. und max.", "modes": { "description": { - "threshold-based-percent-between-minmax": "", - "thresholds-based-absolute-values": "" + "threshold-based-percent-between-minmax": "Wählen Sie den Schwellenwert basierend auf dem Prozentsatz zwischen min/max", + "thresholds-based-absolute-values": "Wählen Sie Schwellenwerte basierend auf den absoluten Werten" }, "label": { - "absolute": "", - "percentage": "" + "absolute": "Absolut", + "percentage": "Prozentsatz" } }, "thresholds-mode": "Schwellenwertmodus", - "value-base": "" + "value-base": "Base" }, "urlpicker-tab": { "label-preview": "Vorschau" @@ -7131,6 +7164,7 @@ "start-time": "Startzeit:" } }, + "share-span": "", "warnings": "Warnungen" }, "span-filters": { @@ -7398,23 +7432,23 @@ "title": "Seite verlassen?" }, "gauge": { - "category-gauge": "", - "description-min-height": "", - "description-min-width": "", - "description-neutral": "", - "description-show-threshold-labels": "", - "description-show-threshold-markers": "", + "category-gauge": "Anzeige", + "description-min-height": "Minimale Zeilenhöhe (horizontale Ausrichtung)", + "description-min-width": "Minimale Spaltenbreite (vertikale Ausrichtung)", + "description-neutral": "Leer lassen, um Min. als neutralen Punkt zu verwenden", + "description-show-threshold-labels": "Schwellenwerte um den Anzeigebalken rendern", + "description-show-threshold-markers": "Rendert die Schwellenwerte als äußeren Balken", "gauge-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Auto", + "label-manual": "Manuell" }, - "name-gauge-size": "", - "name-min-height": "", - "name-min-width": "", - "name-neutral": "", - "name-show-threshold-labels": "", - "name-show-threshold-markers": "", - "placeholder-neutral": "" + "name-gauge-size": "Anzeigegröße", + "name-min-height": "Min. Höhe", + "name-min-width": "Min. Breite", + "name-neutral": "Neutral", + "name-show-threshold-labels": "Schwellenwert-Labels anzeigen", + "name-show-threshold-markers": "Schwellenwert-Markierungen anzeigen", + "placeholder-neutral": "auto" }, "gen-ai": { "apply-suggestion": "Anwenden", @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7748,85 +8185,85 @@ }, "builder": { "axis": { - "category-axis": "", - "color-label": "", + "category-axis": "Achse", + "color-label": "Farbe", "color-options": { - "label-series": "", - "label-text": "" + "label-series": "Reihe", + "label-text": "Text" }, "grid-line-options": { - "label-auto": "", - "label-off": "", - "label-on": "" + "label-auto": "Auto", + "label-off": "Ausgeschaltet", + "label-on": "Eingeschaltet" }, - "name-centered-zero": "", - "name-grid-lines": "", - "name-label": "", - "name-placement": "", - "name-scale": "", - "name-show-border": "", - "name-soft-max": "", - "name-soft-min": "", - "name-width": "", - "placeholder-label": "", - "placeholder-soft-max": "", - "placeholder-soft-min": "", - "placeholder-width": "", + "name-centered-zero": "Zentrierte Null", + "name-grid-lines": "Gitterlinien anzeigen", + "name-label": "Label", + "name-placement": "Platzierung", + "name-scale": "Skalieren", + "name-show-border": "Rand anzeigen", + "name-soft-max": "Soft max.", + "name-soft-min": "Soft min.", + "name-width": "Breite", + "placeholder-label": "Optionaler Text", + "placeholder-soft-max": "Siehe: Standardoptionen > Max.", + "placeholder-soft-min": "Siehe: Standardoptionen > Min.", + "placeholder-width": "Auto", "scale-distribution-editor": { "distribution-options": { - "label-linear": "", - "label-log": "", - "label-symlog": "" + "label-linear": "Linear", + "label-log": "Logarithmisch", + "label-symlog": "Symlog" } } }, "legend": { - "category": "", - "description-values": "", + "category": "Legende", + "description-values": "Wählen Sie Werte oder Berechnungen, die in der Legende angezeigt werden sollen", "mode-options": { - "label-list": "", - "label-table": "" + "label-list": "Liste", + "label-table": "Tabelle" }, - "name-mode": "", - "name-placement": "", - "name-values": "", - "name-visibility": "", - "name-width": "", + "name-mode": "Modus", + "name-placement": "Platzierung", + "name-values": "Werte", + "name-visibility": "Sichtbarkeit", + "name-width": "Breite", "placement-options": { - "label-bottom": "", - "label-right": "" + "label-bottom": "Unten", + "label-right": "Rechts" } }, "stacking": { - "name-stack-series": "" + "name-stack-series": "Reihen stapeln" }, "text": { - "category-text-size": "", - "name-percent-change": "", - "name-title": "", - "name-value": "", - "placeholder-percent-change": "", - "placeholder-title": "", - "placeholder-value": "" + "category-text-size": "Textgröße", + "name-percent-change": "Prozentuale Veränderung", + "name-title": "Titel", + "name-value": "Wert", + "placeholder-percent-change": "Auto", + "placeholder-title": "Auto", + "placeholder-value": "Auto" }, "tooltip": { - "category": "", - "description-hover-proximity": "", + "category": "Tooltip", + "description-hover-proximity": "Wie nah der Cursor an einem Punkt sein muss, um den Tooltip auszulösen, in Pixeln", "modeOptions": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Alles", + "label-hidden": "Ausgeblendet", + "label-single": "Einzeln" }, - "name-hide-zeros": "", - "name-hover-proximity": "", - "name-max-height": "", - "name-max-width": "", - "name-tooltip-mode": "", - "name-values-sort-order": "", + "name-hide-zeros": "Nullen ausblenden", + "name-hover-proximity": "Hover-Nähe", + "name-max-height": "Max. Höhe", + "name-max-width": "Max. Breite", + "name-tooltip-mode": "Tooltip-Modus", + "name-values-sort-order": "Sortierreihenfolge der Werte", "sortOptions": { - "label-ascending": "", - "label-descending": "", - "label-none": "" + "label-ascending": "Aufsteigend", + "label-descending": "Absteigend", + "label-none": "Keine" } } }, @@ -7949,6 +8386,60 @@ "previous-label": "Vorheriger Monat", "select-placeholder": "Datum/Uhrzeit auswählen" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Schließen" }, @@ -7988,19 +8479,33 @@ "tooltip-expand-all": "Alle Zeilen erweitern" }, "matchers-ui": { - "description-field-name-by-regex-matcher": "", - "description-fields-by-query": "", - "description-fields-with-name": "", - "description-fields-with-type": "", - "description-fields-with-value": "", + "description-field-name-by-regex-matcher": "Eigenschaften für Felder festlegen, deren Namen einem Regex entsprechen", + "description-fields-by-query": "Eigenschaften für Felder von einer bestimmten Abfrage festlegen", + "description-fields-with-name": "Eigenschaften für ein bestimmtes Feld festlegen", + "description-fields-with-type": "Eigenschaften für Felder eines bestimmten Typs festlegen (Zahl, String, Boolescher Wert)", + "description-fields-with-value": "Eigenschaften für Felder mit Reduzierbedingung festlegen", "field-name-picker": { - "placeholder": "" + "placeholder": "Feld auswählen" }, - "name-field-name-by-regex-matcher": "", - "name-fields-by-query": "", - "name-fields-with-name": "", - "name-fields-with-type": "", - "name-fields-with-value": "" + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, + "name-field-name-by-regex-matcher": "Felder mit Namen, die Regex entsprechen", + "name-fields-by-query": "Von der Abfrage zurückgegebene Felder", + "name-fields-with-name": "Felder mit Name", + "name-fields-with-type": "Felder mit Typ", + "name-fields-with-value": "Felder mit Werten" }, "menu-item": { "keyboard-shortcut-label": "Tastenkombination" @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Keine Werte", - "pagination-summary": "{{itemsRangeStart}}–{{displayedEnd}} von {{numRows}} Zeilen" + "pagination-summary": "{{itemsRangeStart}}–{{displayedEnd}} von {{numRows}} Zeilen", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tags" @@ -8124,55 +8632,55 @@ "config": { "get-graph-field-options": { "axis-placement": { - "description-auto": "", - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-right": "" + "description-auto": "Erstes Feld links, alles andere rechts", + "label-auto": "Auto", + "label-hidden": "Ausgeblendet", + "label-left": "Links", + "label-right": "Rechts" }, "bar-alignment": { - "description-after": "", - "description-before": "", - "description-center": "" + "description-after": "Danach", + "description-before": "Vorher", + "description-center": "Mitte" }, "draw-style": { - "label-bars": "", - "label-lines": "", - "label-points": "" + "label-bars": "Balken", + "label-lines": "Linien", + "label-points": "Punkte" }, "fill-gradient": { - "description-hue": "", - "description-opacity": "", - "description-scheme": "", - "label-hue": "", - "label-none": "", - "label-opacity": "", - "label-scheme": "" + "description-hue": "Kleiner Farbverlauf", + "description-opacity": "Fülldeckkraftverlauf aktivieren", + "description-scheme": "Farbschema zur Festlegung des Farbverlaufs verwenden", + "label-hue": "Farbton", + "label-none": "Keine", + "label-opacity": "Deckkraft", + "label-scheme": "Schema" }, "line-interpolation": { - "description-linear": "", - "description-smooth": "", - "description-step-after": "", - "description-step-before": "" + "description-linear": "Linear", + "description-smooth": "Glatt", + "description-step-after": "Schritt danach", + "description-step-before": "Schritt davor" }, "show-points": { - "description-auto": "", - "label-always": "", - "label-auto": "", - "label-never": "" + "description-auto": "Punkte anzeigen, wenn die Dichte niedrig ist", + "label-always": "Immer", + "label-auto": "Auto", + "label-never": "Nie" }, "stacking": { - "label-100": "", - "label-normal": "", - "label-off": "" + "label-100": "100 %", + "label-normal": "Normal", + "label-off": "Ausgeschaltet" }, "thresholds-display-mode": { - "label-dashed-lines": "", - "label-filled-regions": "", - "label-filled-regions-and-dashed-lines": "", - "label-filled-regions-and-lines": "", - "label-lines": "", - "label-off": "" + "label-dashed-lines": "Als Linien (gestrichelt)", + "label-filled-regions": "Als gefüllte Bereiche", + "label-filled-regions-and-dashed-lines": "Als gefüllte Bereiche und Linien (gestrichelt)", + "label-filled-regions-and-lines": "Als gefüllte Bereiche und Linien", + "label-lines": "Als Linien", + "label-off": "Ausgeschaltet" } } } @@ -8219,78 +8727,78 @@ }, "heatmap": { "calculate-from-data-options": { - "label-no": "", - "label-yes": "" + "label-no": "Nein", + "label-yes": "Ja" }, - "category-cell-display": "", - "category-colors": "", - "category-exemplars": "", - "category-heatmap": "", - "category-legend": "", - "category-tooltip": "", - "category-y-axis": "", + "category-cell-display": "Zellenanzeige", + "category-colors": "Farben", + "category-exemplars": "Exemplare", + "category-heatmap": "Heatmap", + "category-legend": "Legende", + "category-tooltip": "Tooltip", + "category-y-axis": "Y-Achse", "mode-options": { - "label-opacity": "", - "label-scheme": "" + "label-opacity": "Deckkraft", + "label-scheme": "Schema" }, - "name-axis-label": "", - "name-axis-width": "", - "name-calculate-from-data": "", - "name-cell-gap": "", - "name-color": "", - "name-decimals": "", - "name-end-color-at-value": "", - "name-exponent": "", - "name-hide-cells-gt": "", - "name-hide-cells-lt": "", - "name-max-height": "", - "name-max-value": "", - "name-max-width": "", - "name-min-value": "", - "name-mode": "", - "name-placement": "", - "name-reverse": "", - "name-scale": "", - "name-scheme": "", - "name-show-color-scale": "", - "name-show-histogram": "", - "name-show-legend": "", - "name-start-color-from-value": "", - "name-steps": "", - "name-tick-alignment": "", - "name-tooltip-mode": "", - "name-unit": "", - "name-value-name": "", - "name-y-axis-scale": "", - "placeholder-axis-label": "", - "placeholder-axis-width": "", - "placeholder-decimals": "", - "placeholder-end-color-at-value": "", - "placeholder-hide-cells-gt": "", - "placeholder-hide-cells-lt": "", - "placeholder-max-value": "", - "placeholder-min-value": "", - "placeholder-start-color-from-value": "", - "placeholder-value-name": "", + "name-axis-label": "Achsenlabel", + "name-axis-width": "Achsenbreite", + "name-calculate-from-data": "Aus Daten berechnen", + "name-cell-gap": "Zellenlücke", + "name-color": "Farbe", + "name-decimals": "Dezimalstellen", + "name-end-color-at-value": "Farbskala bei folgendem Wert beenden", + "name-exponent": "Exponent", + "name-hide-cells-gt": "Ausblenden von Zellen mit Werten >=", + "name-hide-cells-lt": "Ausblenden von Zellen mit Werten <=", + "name-max-height": "Max. Höhe", + "name-max-value": "Max. Wert", + "name-max-width": "Max. Breite", + "name-min-value": "Min. Wert", + "name-mode": "Modus", + "name-placement": "Platzierung", + "name-reverse": "Rückwärts", + "name-scale": "Skalieren", + "name-scheme": "Schema", + "name-show-color-scale": "Farbskala anzeigen", + "name-show-histogram": "Histogramm anzeigen (Y-Achse)", + "name-show-legend": "Legende anzeigen", + "name-start-color-from-value": "Farbskala ab folgendem Wert beginnen", + "name-steps": "Schritte", + "name-tick-alignment": "Teilstrich-Ausrichtung", + "name-tooltip-mode": "Tooltip-Modus", + "name-unit": "Einheit", + "name-value-name": "Wertname", + "name-y-axis-scale": "Y-Achsenskala", + "placeholder-axis-label": "Auto", + "placeholder-axis-width": "Auto", + "placeholder-decimals": "Auto", + "placeholder-end-color-at-value": "Auto (max.)", + "placeholder-hide-cells-gt": "Keine", + "placeholder-hide-cells-lt": "Keine", + "placeholder-max-value": "Auto", + "placeholder-min-value": "Auto", + "placeholder-start-color-from-value": "Auto (min.)", + "placeholder-value-name": "Wert", "placement-options": { - "label-hidden": "", - "label-left": "", - "label-right": "" + "label-hidden": "Ausgeblendet", + "label-left": "Links", + "label-right": "Rechts" }, "scale-options": { - "label-exponential": "", - "label-linear": "" + "label-exponential": "Exponentiell", + "label-linear": "Linear" }, "tick-alignment-options": { - "label-auto": "", - "label-bottom": "", - "label-middle": "", - "label-top": "" + "label-auto": "Auto", + "label-bottom": "Unten (GE)", + "label-middle": "Mitte", + "label-top": "Oben (LE)" }, "tooltip-mode-options": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Alles", + "label-hidden": "Ausgeblendet", + "label-single": "Einzeln" } }, "help-modal": { @@ -8350,14 +8858,14 @@ "troubleshooting-help": "Um Hilfe bei der Fehlerbehebung anzufordern, senden Sie bitte einen Screenshot dieses Panels an den technischen Support von Grafana Labs. Der Snapshot enthält Abfrageantwortdaten und Panel-Einstellungen." }, "histogram": { - "category-histogram": "", + "category-histogram": "Histogramm", "histogram-panel": { "no-histogram-found-in-response": "Kein Histogramm in der Antwort gefunden" }, - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-width": "", - "name-stacking": "" + "name-fill-opacity": "Fülldeckkraft", + "name-gradient-mode": "Farbverlaufsmodus", + "name-line-width": "Linienbreite", + "name-stacking": "Stapeln" }, "inspector": { "inspect-data-tab": { @@ -8738,16 +9246,16 @@ } }, "logs": { - "category-logs": "", + "category-logs": "Logs", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Exakt", + "label-none": "Keine", + "label-numbers": "Zahlen", + "label-signature": "Signatur" }, - "description-enable-infinite-scrolling": "", - "description-enable-syntax-highlighting": "", - "description-show-controls": "", + "description-enable-infinite-scrolling": "Experimentell. Fordern Sie weitere Ergebnisse an, indem Sie zum Ende der Logs-Liste scrollen.", + "description-enable-syntax-highlighting": "Verwenden Sie eine vordefinierte Syntax-Farbgrammatik, um relevante Teile der Log-Linien hervorzuheben", + "description-show-controls": "Anzeige von Steuerelementen für den Sprung zur letzten oder ersten Log-Linie und Filtern nach Log-Ebene", "fields": { "type": { "loki": { @@ -8761,8 +9269,8 @@ } }, "font-size-options": { - "label-default": "", - "label-small": "" + "label-default": "Standard", + "label-small": "Klein" }, "get-dataframe-fields": { "vars": { @@ -8809,58 +9317,64 @@ "tooltip-error": "Fehler: {{errorMessage}}" }, "log-line-details": { - "clear-search": "", - "close": "", - "copy-shortlink": "", - "copy-to-clipboard": "", + "clear-search": "Löschen", + "close": "Logdetails schließen", + "copy-shortlink": "Shortlink kopieren", + "copy-to-clipboard": "In die Zwischenablage kopieren", "displayed-fields-section": "", "fields": { - "adhoc-statistics": "", - "copy-value-to-clipboard": "", - "filter-out": "", - "filter-out-query": "", + "adhoc-statistics": "Ad-hoc-Statistiken", + "copy-value-to-clipboard": "Wert in die Zwischenablage kopieren", + "filter-out": "Wert herausfiltern", + "filter-out-query": "Wert in Abfrage {{query}} herausfiltern", "toggle-field-button": { - "field-instead-message": "", - "hide-this-field": "" + "field-instead-message": "Dieses Feld anstelle der Nachricht anzeigen", + "hide-this-field": "Dieses Feld ausblenden" } }, - "fields-section": "", - "hide-log-line": "", + "fields-section": "Felder", + "hide-log-line": "Log-Linie ausblenden", "inline-mode": "", - "links-section": "", + "links-section": "Links", "log-line-field": "", - "log-line-section": "", + "log-line-section": "Log-Linie", "move-displayed-field-down": "", "move-displayed-field-up": "", - "no-details": "", - "pin-line": "", + "no-details": "Keine anzuzeigenden Felder.", + "open-assistant": "", + "pin-line": "Log anheften", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { - "no-results": "" + "no-results": "Keine anzuzeigenden Ergebnisse." }, - "search-placeholder": "", - "show-context": "", - "show-log-line": "", + "search-placeholder": "Feldnamen und Werte durchsuchen", + "show-context": "Kontext anzeigen", + "show-log-line": "Log-Linie anzeigen", "sidebar-mode": "", - "unpin-line": "" + "unpin-line": "Log nicht mehr anheften" }, "log-line-menu": { "copy-link": "Link zur Protokollzeile kopieren", "copy-log": "Protokollzeile kopieren", "hide-details": "Logdetails anzeigen", "icon-label": "Protokollmenü", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Protokoll anheften", "show-context": "Kontext anzeigen", "show-details": "Logdetails ausblenden", "unpin-from-outline": "Protokoll nicht mehr anheften" }, "log-list-search": { - "close": "", - "filter": "", - "info": "", - "input-placeholder": "", - "next": "", - "prev": "" + "close": "Suche schließen", + "filter": "Entsprechende Logs filtern", + "info": "Clientseitige Suche nach Strings innerhalb der angezeigten Logs. Nicht zu verwechseln mit Abfragefiltern. Verwenden Sie diese Komponente, um bestimmte Strings in Ihren Log-Ergebnissen zu suchen.", + "input-placeholder": "In Logs suchen", + "next": "Nächstes Ergebnis", + "prev": "Vorheriges Ergebnis" }, "log-row-context-modal": { "error-loading-log-more-logs": "Fehler beim Laden weiterer Logs.", @@ -8915,7 +9429,7 @@ "escape-newlines": "Falsch dargestellte Zeilenumbrüche und Tab-Sequenzen in Log-Zeilen korrigieren", "font-size-default": "Kleine Schriftgröße verwenden", "font-size-small": "Standardschriftgröße verwenden", - "hide-search": "", + "hide-search": "Suche schließen", "hide-timestamps": "Zeitstempel ausblenden", "hide-unique-labels": "Eindeutige Labels ausblenden", "newest-first": "Sortiert nach neuesten Logs zuerst – klicken Sie, um die ältesten zuerst anzuzeigen", @@ -8924,7 +9438,7 @@ "remove-escaping": "Escaping entfernen", "scroll-bottom": "Nach unten scrollen", "scroll-top": "Nach oben scrollen", - "show-search": "", + "show-search": "Ergebnis der Suche in Logs", "show-timestamps": "Zeitstempel anzeigen", "show-unique-labels": "Eindeutige Labels anzeigen", "unwrap-lines": "Zeilenumbruch aufheben", @@ -8942,26 +9456,26 @@ "common-labels": "Allgemeine Labels:" } }, - "name-common-labels": "", - "name-deduplication": "", + "name-common-labels": "Allgemeine Labels", + "name-deduplication": "Deduplizierung", "name-details-mode": "", "name-details-options": { "label-inline": "", "label-sidebar": "" }, - "name-enable-infinite-scrolling": "", - "name-enable-log-details": "", - "name-enable-syntax-highlighting": "", - "name-font-size": "", - "name-order": "", - "name-prettify-json": "", - "name-show-controls": "", - "name-time": "", - "name-unique-labels": "", - "name-wrap-lines": "", + "name-enable-infinite-scrolling": "Unbegrenztes Scrollen aktivieren", + "name-enable-log-details": "Log-Details aktivieren", + "name-enable-syntax-highlighting": "Syntaxhervorhebung aktivieren", + "name-font-size": "Schriftgröße", + "name-order": "Reihenfolge", + "name-prettify-json": "JSON formatieren", + "name-show-controls": "Steuerelemente anzeigen", + "name-time": "Zeit", + "name-unique-labels": "Eindeutige Labels", + "name-wrap-lines": "Zeilen umbrechen", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Neueste zuerst", + "label-oldest-first": "Älteste zuerst" }, "out-of-range-message": { "end-of-the-selected-time-range": "Ende des ausgewählten Zeitbereichs." @@ -8975,7 +9489,7 @@ "un-themed-log-details": { "aria-label-data-links": "Datenlinks", "aria-label-fields": "Felder", - "aria-label-line": "", + "aria-label-line": "Log-Linie", "aria-label-log-level": "Log-Stufe", "aria-label-no-details": "Keine Angaben" }, @@ -8996,27 +9510,27 @@ } }, "logs-new": { - "category-logs": "", + "category-logs": "Logs", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Exakt", + "label-none": "Keine", + "label-numbers": "Zahlen", + "label-signature": "Signatur" }, - "description-infinite-scrolling": "", - "description-show-controls": "", - "description-syntax-highlighting": "", - "name-deduplication": "", - "name-enable-log-details": "", - "name-infinite-scrolling": "", - "name-order": "", - "name-show-controls": "", - "name-syntax-highlighting": "", - "name-time": "", - "name-wrap-lines": "", + "description-infinite-scrolling": "Experimentell. Fordern Sie weitere Ergebnisse an, indem Sie zum Ende der Logs-Liste scrollen.", + "description-show-controls": "Anzeige von Steuerelementen für den Sprung zur letzten oder ersten Log-Linie und Filtern nach Log-Ebene", + "description-syntax-highlighting": "Verwenden Sie eine vordefinierte Syntax-Farbgrammatik, um relevante Teile der Log-Linien hervorzuheben", + "name-deduplication": "Deduplizierung", + "name-enable-log-details": "Log-Details aktivieren", + "name-infinite-scrolling": "Unbegrenztes Scrollen aktivieren", + "name-order": "Reihenfolge", + "name-show-controls": "Steuerelemente anzeigen", + "name-syntax-highlighting": "Syntaxhervorhebung aktivieren", + "name-time": "Zeit", + "name-wrap-lines": "Zeilen umbrechen", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Neueste zuerst", + "label-oldest-first": "Älteste zuerst" } }, "manage-dashbaords": { @@ -9682,7 +10196,9 @@ "aria-label": "Hilfe" }, "invite-user": { - "invite-new-member-button": "Neues Mitglied einladen" + "invite-button": "", + "invite-new-member-button": "Neues Mitglied einladen", + "invite-tooltip": "" }, "item": { "add-bookmark": "Zu Lesezeichen hinzufügen", @@ -9714,12 +10230,12 @@ "rss-button": "Das Neueste aus dem Blog" }, "news": { - "category-news": "", - "description-show-image": "", - "description-url": "", + "category-news": "Nachrichten", + "description-show-image": "Steuert, ob das Social-Media-Bild (og:image) des Nachrichtenelements über dem Textinhalt angezeigt wird", + "description-url": "Unterstützt RSS- und Atom-Feeds", "link-title": "Zum Grafana-Labs-Blog", - "name-show-image": "", - "name-url": "", + "name-show-image": "Bild anzeigen", + "name-url": "URL", "news-panel": { "body-error-loading-rss-feed": "Achten Sie darauf, dass die Feed-URL korrekt ist und CORS auf dem Server richtig konfiguriert ist. Weitere Informationen finden Sie in der <2>Dokumentation zum Nachrichten-Panel.", "loading": "Wird geladen ...", @@ -9728,27 +10244,27 @@ "title": "Das Neueste aus dem Blog" }, "node-graph": { - "category-edges": "", - "category-node-graph": "", - "category-nodes": "", + "category-edges": "Ränder", + "category-node-graph": "Knotendiagramm", + "category-nodes": "Knoten", "layout-algorithm-options": { - "description-force": "", - "description-grid": "", - "description-layered": "", - "label-force": "", - "label-grid": "", - "label-layered": "" + "description-force": "Kraftgesteuertes Layout verwenden", + "description-grid": "Rasterlayout verwenden", + "description-layered": "Geschichtetes Layout verwenden", + "label-force": "Kraft", + "label-grid": "Raster", + "label-layered": "Geschichtet" }, - "name-arc-sections": "", - "name-layout-algorithm": "", - "name-main-stat-unit": "", - "name-secondary-stat-unit": "", - "name-zoom-mode": "", + "name-arc-sections": "Bogenabschnitte", + "name-layout-algorithm": "Layout-Algorithmus", + "name-main-stat-unit": "Einheit für Hauptstatistik", + "name-secondary-stat-unit": "Einheit für Sekundärstatistik", + "name-zoom-mode": "Zoom-Modus", "zoom-mode-options": { - "description-cooperative": "", - "description-greedy": "", - "label-cooperative": "", - "label-greedy": "" + "description-cooperative": "Ermöglicht Ihnen das normale Scrollen der Seite", + "description-greedy": "Reagiert auf alle Zoom-Gesten", + "label-cooperative": "Kooperativ", + "label-greedy": "Greedy" } }, "nodeGraph": { @@ -9827,40 +10343,40 @@ }, "registry": { "get-all-option-editors": { - "description-time-zone": "", - "name-time-zone": "" + "description-time-zone": "Auswahl der Zeitzone", + "name-time-zone": "Zeitzone" }, "standard-field-condigs": { - "category-data-links": "" + "category-data-links": "Datenlinks und Aktionen" }, "standard-field-configs": { - "category": "", - "category-thresholds": "", - "category-value-mappings": "", - "description-display-name": "", - "description-field-min-max": "", - "description-max": "", - "description-min": "", - "description-no-value": "", - "description-value-mappings": "", - "name-actions": "", - "name-ad-hoc": "", - "name-color-scheme": "", - "name-data-links": "", - "name-decimals": "", - "name-display-name": "", - "name-field-min-max": "", - "name-max": "", - "name-min": "", - "name-no-value": "", - "name-thresholds": "", - "name-unit": "", - "name-value-mappings": "", - "placeholder-decimals": "", - "placeholder-display-name": "", - "placeholder-max": "", - "placeholder-min": "", - "placeholder-unit": "" + "category": "Standardoptionen", + "category-thresholds": "Schwellenwerte", + "category-value-mappings": "Wertezuordnungen", + "description-display-name": "Feld- oder Reihenname ändern", + "description-field-min-max": "Min. Max. pro Feld berechnen", + "description-max": "Leer lassen, um anhand aller Werte zu berechnen", + "description-min": "Leer lassen, um anhand aller Werte zu berechnen", + "description-no-value": "Was angezeigt wird, wenn kein Wert vorhanden ist", + "description-value-mappings": "Ändern Sie den Anzeigetext je nach Eingabewert", + "name-actions": "Aktionen", + "name-ad-hoc": "Ad-hoc filterbar", + "name-color-scheme": "Farbschema", + "name-data-links": "Datenlinks", + "name-decimals": "Dezimalstellen", + "name-display-name": "Anzeigename", + "name-field-min-max": "Feld Min./Max.", + "name-max": "Max.", + "name-min": "Min.", + "name-no-value": "Kein Wert", + "name-thresholds": "Schwellenwerte", + "name-unit": "Einheit", + "name-value-mappings": "Wertezuordnungen", + "placeholder-decimals": "auto", + "placeholder-display-name": "keine", + "placeholder-max": "auto", + "placeholder-min": "auto", + "placeholder-unit": "keine" } }, "units": { @@ -9969,13 +10485,13 @@ }, "panel-data-error-view": { "missing-value": { - "number": "", - "string": "", - "time": "", - "unknown": "" + "number": "In den Daten fehlt ein Zahlenfeld", + "string": "In den Daten fehlt ein String-Feld", + "time": "In den Daten fehlt ein Zeitfeld", + "unknown": "Daten können nicht visualisiert werden" }, "no-value": { - "default": "" + "default": "Keine Daten" }, "open-visualization-suggestions": "Visualisierungsvorschläge öffnen", "switch-to-table": "Zur Tabelle wechseln" @@ -10015,25 +10531,25 @@ "select-placeholder": "Nach Typ filtern" }, "piechart": { - "category-legend": "", - "category-pie-chart": "", - "description-labels": "", - "description-pie-chart-type": "", + "category-legend": "Legende", + "category-pie-chart": "Kreisdiagramm", + "description-labels": "Wählen Sie die Labels aus, die im Kreisdiagramm angezeigt werden sollen", + "description-pie-chart-type": "Wie das Kreisdiagramm gerendert werden soll", "labels-options": { - "label-name": "", - "label-percent": "", - "label-value": "" + "label-name": "Name", + "label-percent": "Prozent", + "label-value": "Wert" }, "legend-values-options": { - "label-percent": "", - "label-value": "" + "label-percent": "Prozent", + "label-value": "Wert" }, - "name-labels": "", - "name-legend-values": "", - "name-pie-chart-type": "", + "name-labels": "Labels", + "name-legend-values": "Legendenwerte", + "name-pie-chart-type": "Kreisdiagrammtyp", "pie-chart-type-options": { - "label-donut": "", - "label-pie": "" + "label-donut": "Donut", + "label-pie": "Kreis" } }, "playlist": { @@ -10271,7 +10787,7 @@ }, "install-controls-warning": { "body-not-published": "Dieses Plugin wird nicht bei <2>grafana.com/plugins veröffentlicht und kann nicht über den Katalog verwaltet werden.", - "enterprise-plugin-info": "", + "enterprise-plugin-info": "Dieses Plugin ist nur in Grafana Cloud und Grafana Enterprise verfügbar.", "learn-more": "Mehr erfahren", "no-valid-grafana-enterprise-license-detected": "Keine gültige Lizenz für Grafana Enterprise erkannt.", "title-dev-alert": "Dies ist ein Development-Build des Plugins und kann nicht deinstalliert werden.", @@ -10429,28 +10945,50 @@ }, "provisioned-resource-form": { "save-or-delete-resource-shared-fields": { - "comment-placeholder-describe-changes-optional": "", - "description-branch-name-in-git-hub": "", + "comment-placeholder-describe-changes-optional": "Fügen Sie eine Notiz hinzu, um Ihre Änderungen zu beschreiben (optional)", + "description-branch-name-in-git-hub": "Branch-Name in GitHub", "description-inside-repository": "", - "label-branch": "", - "label-comment": "", - "label-path": "", - "label-workflow": "" + "label-branch": "Branch", + "label-comment": "Kommentar", + "label-path": "Pfad", + "label-workflow": "Workflow" } }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Diese Funktion befindet sich momentan in der aktiven Entwicklung. Für die bestmögliche Nutzererfahrung und die neuesten Verbesserungen empfehlen wir die Nutzung von <2>Nightly Build von Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Fügen Sie einen eindeutigen Namen für diese Repository-Verbindung hinzu", "empty": "Leer", @@ -10460,8 +10998,12 @@ "label-display-name": "Anzeigename", "placeholder-my-repository-connection": "Meine Repository-Verbindung", "text-loading-resource-information": "Ressourceninformationen werden geladen ...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} Dateien", - "files-count_other": "{{count}} Dateien" + "files-count_other": "{{count}} Dateien", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Überprüfen" @@ -10472,36 +11014,20 @@ "button-save": "Speichern", "button-saving": "Wird gespeichert …", "description-enabled": "Sobald das automatische Abrufen aktiviert ist, kann das Ziel nicht mehr geändert werden.", - "description-path": "Pfad zu einem Unterverzeichnis im Git-Repository", "description-read-only": "Ressourcen können über Grafana nicht geändert werden.", - "description-repository-url": "Geben Sie die GitHub-Repository-URL ein", "description-title": "Ein menschenlesbarer Name für die Konfiguration", "error-required": "Dieses Feld ist erforderlich.", "error-save-repository": "Speichern der Repository-Einstellungen fehlgeschlagen", - "error-valid-github-url": "Bitte geben Sie eine gültige GitHub-Repository-URL ein", "label-automatic-pulling": "Automatisches Pulling", - "label-branch": "Branch", "label-enabled": "Aktiviert", - "label-github-token": "GitHub-Token", "label-interval-seconds": "Intervall (Sekunden)", - "label-local-path": "Lokaler Pfad", - "label-path": "Pfad", - "label-pr-workflow": "Pull-Request-Option beim Speichern aktivieren", "label-repository-type": "Repository-Typ", - "label-repository-url": "Repository-URL", "label-target": "Ziel", "label-title": "Titel", "option-entire-instance": "Gesamte Instanz", - "option-github": "GitHub", - "option-local": "Lokal", "option-managed-folder": "Verwalteter Ordner", - "placeholder-branch": "primär", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Meine Konfiguration", - "placeholder-select-repository-type": "Repository-Typ auswählen" + "placeholder-my-config": "Meine Konfiguration" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Konfigurieren Sie Webhooks, um sofortige Updates in Grafana zu erhalten, sobald Änderungen übernommen werden. Überprüfen und genehmigen Sie Änderungen mithilfe von Pull-Requests, bevor sie übernommen werden.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Konfigurieren", - "configure-file": "Datei-Bereitstellung konfigurieren", - "configure-git-sync": "Git Sync konfigurieren", "repository-limit-info-alert": "Repository-Limit erreicht ({{count}})" }, - "connect-step": { - "description-branch": "Zweig, der für das GitHub-Repository verwendet wird", - "description-github-path": "Dies ist der Pfad zu einem Unterverzeichnis in Ihrem GitHub-Repository, wo Dashboards gespeichert und bereitgestellt werden", - "description-paste-your-git-hub-personal-access-token": "Fügen Sie Ihren persönlichen Zugriffstoken von GitHub ein", - "description-repository-url": "Fügen Sie die URL Ihres GitHub-Repositorys ein", - "error-field-required": "Dieses Feld ist erforderlich.", - "error-invalid-github-url": "Bitte geben Sie eine gültige GitHub-Repository-URL ein", - "label-access-token": "GitHub-Zugriffstoken", - "label-branch": "Zweigname", - "label-local-path": "Lokaler Pfad", - "label-path": "Pfad zum Unterverzeichnis im Repository", - "label-repository-url": "GitHub-Repository-URL", - "placeholder-branch": "primär", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Löschen", "confirm-delete-repository": "Sind Sie sicher, dass Sie die Repository-Konfiguration löschen möchten?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Sofortige Updates und Pull-Requests mit Webhooks.", "title-visual-previews-in-pull-requests": "Visuelle Vorschauen in Pull-Requests mit Bildwiedergabe" }, + "error-title-default": "", "expanded-row": { "job-specification": "Auftragsspezifikation", - "summary": "Zusammenfassung", - "title-error": "Fehler" + "summary": "Zusammenfassung" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Benötigte Funktionsschalter einrichten" }, - "manage-dashboards-provision-updates-automatically": "Verwalten Sie Dashboards als Code in GitHub und stellen Sie Updates automatisch bereit", - "manage-your-dashboards-with-remote-provisioning": "Erste Schritte mit GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Speichern Sie Dashboards im versionskontrollierten Speicher für eine bessere Organisation und Verlaufsverfolgung" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Fügt eine Bildvorschau der Dashboard-Änderungen bei Pull-Requests hinzu. Bilder Ihrer Grafana-Dashboards werden in Ihrem Git-Repository bereitgestellt und sind für jede Person mit Repository-Zugriff sichtbar.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Erfordert Bild-Rendering. <2>Bild-Rendering einrichten", - "description-often-shall-instance-updates-git-hub": "Wie oft soll die Instanz Updates von GitHub abrufen?", - "description-pr-enable-description": "Ermöglicht Nutzern, beim Speichern von Änderungen zu wählen, ob ein Pull-Request geöffnet werden soll. Wenn das Repository keine direkten Änderungen am Hauptzweig zulässt, ist möglicherweise dennoch ein Pull-Request erforderlich.", + "description-preview-requirements": "", "description-read-only": "Ressourcen können über Grafana nicht geändert werden.", - "description-webhooks-enable": "Ermöglicht Nutzern, beim Speichern von Änderungen zu wählen, ob ein Pull-Request geöffnet werden soll. Wenn das Repository keine direkten Änderungen am Hauptzweig zulässt, ist möglicherweise dennoch ein Pull-Request erforderlich.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Dashboard-Vorschau in Pull-Requests aktivieren", - "label-pr-workflow": "Pull-Request-Option beim Speichern aktivieren", + "label-generate-dashboard-previews": "", "label-read-only": "Nur lesen", - "label-update-instance-interval-seconds": "Intervall der Instanz-Updates (Sekunden)", - "placeholder": "60", - "text-setup-later": "Sie können dies jederzeit später einrichten", - "title-enhance-github": "Verbessern Sie Ihre Nutzererfahrung mit GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Keine passenden Ergebnisse zu Ihrer Abfrage", @@ -10640,6 +11145,57 @@ "header": "Bereitstellung", "subtitle-provisioning-feature": "Sehen und verwalten Sie Ihre Bereitstellungsverbindungen" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Nicht gefunden" }, @@ -10666,9 +11222,17 @@ "starting": "Wird gestartet ...", "status": { "title-error-running-job": "Fehler bei der Ausführung des Auftrags", - "title-job-completed-successfully": "Auftrag erfolgreich abgeschlossen" + "title-warning-running-job": "" }, - "summary": "Zusammenfassung" + "summary": "Zusammenfassung", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Einstellungen", - "source-code": "Quellcode" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Veralteter Speicher", "title-queued-for-deletion": "Zum Löschen in die Warteschlange gestellt" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Basis", "dashboard-preview": "Dashboard-Vorschau", @@ -10788,6 +11370,11 @@ "label-current-step": "Aktueller Schritt", "label-pending-step": "Ausstehender Schritt" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Konfiguration bearbeiten", "button-edit": "Bearbeiten", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Ein fehlerhaftes Repository kann nicht abgerufen werden" }, "synchronize-step": { - "error-no-job-id": "Auftrag konnte nicht gestartet werden", - "error-no-repository-name": "Kein Repository-Name angegeben", - "error-starting-job": "Fehler beim Starten des Auftrags", "synchronization-description": "Commits für jeden historischen Wert erfassen", "synchronization-options": "Synchronisierungsoptionen" }, @@ -10809,6 +11393,7 @@ "go-to": "Weiter zu", "make-sure": "Achten Sie darauf, dass diese Berechtigungen berücksichtigt werden" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Während dieses Vorgangs können keine Ressourcen erstellt, bearbeitet oder gelöscht werden. Im letzten Schritt werden sie entfernt.", "alert-point-2": "Sobald die Bereitstellung abgeschlossen ist, werden die Ressourcen wieder angezeigt und über einen externen Speicher verwaltet.", @@ -11715,112 +12300,112 @@ }, "stat": { "add-orientation-option": { - "description-orientation": "", - "name-orientation": "", + "description-orientation": "Layout-Ausrichtung", + "name-orientation": "Ausrichtung", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-vertical": "" + "label-auto": "Auto", + "label-horizontal": "Horizontal", + "label-vertical": "Vertikal" } }, "add-standard-data-reduce-options": { - "category-value-options": "", - "description-calculation": "", - "description-fields": "", - "description-limit": "", - "description-show": "", + "category-value-options": "Wertoptionen", + "description-calculation": "Wählen Sie eine Reduktionsfunktion/Berechnung", + "description-fields": "Wählen Sie die Felder aus, die im Panel enthalten sein sollen", + "description-limit": "Maximale Anzahl der anzuzeigenden Zeilen", + "description-show": "Berechnen Sie einen einzelnen Wert pro Spalte oder Reihe oder zeigen Sie jede Zeile an", "fields-options": { - "label-all-fields": "", - "label-numeric-fields": "" + "label-all-fields": "Alle Felder", + "label-numeric-fields": "Numerische Felder" }, - "name-calculation": "", - "name-fields": "", - "name-limit": "", - "name-show": "", + "name-calculation": "Berechnung", + "name-fields": "Felder", + "name-limit": "Grenze", + "name-show": "Anzeigen", "show-options": { - "label-all-values": "", - "label-calculate": "" + "label-all-values": "Alle Werte", + "label-calculate": "Berechnen" } }, - "category-stat-styles": "", + "category-stat-styles": "Statistikstile", "color-mode-options": { - "label-background-gradient": "", - "label-background-solid": "", - "label-none": "", - "label-value": "" + "label-background-gradient": "Hintergrundfabverlauf", + "label-background-solid": "Hintergrund einfarbig", + "label-none": "Keine", + "label-value": "Wert" }, - "description-graph-mode": "", - "description-text-mode": "", + "description-graph-mode": "Modus für Statistik-Panel-Diagramm/Sparkline", + "description-text-mode": "Legen Sie fest, ob Name und Wert angezeigt werden oder nur der Name", "graph-mode": { "options": { - "label-area": "", - "label-none": "" + "label-area": "Bereich", + "label-none": "Keine" } }, "name-color-modcolor-mode-options": { - "label": "" + "label": "Farbmodus" }, - "name-graph-mode": "", - "name-show-percent-change": "", - "name-text-alignment": "", - "name-text-mode": "", - "name-wide-layout": "", - "percent-change-color-mode": "", + "name-graph-mode": "Diagrammmodus", + "name-show-percent-change": "Prozentuale Veränderung anzeigen", + "name-text-alignment": "Textausrichtung", + "name-text-mode": "Textmodus", + "name-wide-layout": "Breites Layout", + "percent-change-color-mode": "Farbmodus für prozentuale Veränderung", "percent-change-color-mode-options": { - "label-inverted": "", - "label-same-as-value": "", - "label-standard": "" + "label-inverted": "Invertiert", + "label-same-as-value": "Identisch mit Wert", + "label-standard": "Standard" }, "text-alignment-options": { - "label-auto": "", - "label-center": "" + "label-auto": "Auto", + "label-center": "Mitte" }, "text-mode-options": { - "label-auto": "", - "label-name": "", - "label-none": "", - "label-value": "", - "label-value-and-name": "" + "label-auto": "Auto", + "label-name": "Name", + "label-none": "Keine", + "label-value": "Wert", + "label-value-and-name": "Wert und Name" }, "wide-layout-options": { - "label-off": "", - "label-on": "" + "label-off": "Ausgeschaltet", + "label-on": "Eingeschaltet" } }, "state-timeline": { "align-values-options": { - "label-center": "", - "label-left": "", - "label-right": "" + "label-center": "Mitte", + "label-left": "Links", + "label-right": "Rechts" }, - "category-state-timeline": "", - "name-align-values": "", - "name-connect-null-values": "", - "name-disconnect-values": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-merge-equal-consecutive-values": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-state-timeline": "Statuszeitleiste", + "name-align-values": "Werte ausrichten", + "name-connect-null-values": "Nullwerte verbinden", + "name-disconnect-values": "Werte trennen", + "name-fill-opacity": "Fülldeckkraft", + "name-line-width": "Linienbreite", + "name-merge-equal-consecutive-values": "Gleiche aufeinanderfolgende Werte verbinden", + "name-page-size": "Seitengröße (Seitennummerierung aktivieren)", + "name-row-height": "Zeilenhöhe", + "name-show-values": "Werte anzeigen", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Immer", + "label-auto": "Auto", + "label-never": "Nie" } }, "status-history": { - "category-status-history": "", - "name-column-width": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-status-history": "Statusverlauf", + "name-column-width": "Spaltenbreite", + "name-fill-opacity": "Fülldeckkraft", + "name-line-width": "Linienbreite", + "name-page-size": "Seitengröße (Seitennummerierung aktivieren)", + "name-row-height": "Zeilenhöhe", + "name-show-values": "Werte anzeigen", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Immer", + "label-auto": "Auto", + "label-never": "Nie" }, "status-history-panel": { "too-many-points_one": "Zu viele Punkte, um sie richtig darzustellen. <1>Aktualisieren Sie die Abfrage, damit weniger Punkte zurückgegeben werden. <3>({{count}} Punkte erhalten)", @@ -11860,13 +12445,13 @@ "label-gauge-display-mode": "Messanzeigemodus", "label-value-display": "Wertanzeige" }, - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Zellenoptionen", + "category-table": "Tabelle", + "category-table-footer": "Tabellenfußzeile", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Groß", + "label-medium": "Mittel", + "label-small": "Klein" }, "color-background-cell-options-editor": { "description-apply-to-entire-row": "Wenn dies ausgewählt ist, wird die gesamte Zeile so eingefärbt, wie es diese Zelle wäre.", @@ -11876,26 +12461,25 @@ }, "label-apply-to-entire-row": "Auf gesamte Zeile anwenden", "label-background-display-mode": "Hintergrundanzeigemodus", - "label-wrap-text": "Text umbrechen", "wrap-text": "Text umbrechen" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Auto", + "label-center": "Mitte", + "label-left": "Links", + "label-right": "Rechts" }, "container": { "content": "Wenn zu viele Spalten in einer Tabelle angezeigt werden, kann dies die Leistung beeinträchtigen und die Daten können schwieriger zu lesen sein. Präzisieren Sie, sofern möglich, Ihre Abfragen.", "show-all-series": "Alle Spalten anzeigen", "show-only-series": "Nur {{MAX_NUMBER_OF_COLUMNS}} Spalten werden angezeigt" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", + "description-calculation": "Wählen Sie eine Reduktionsfunktion/Berechnung", + "description-cell-value-inspect": "Zellwertprüfung in einem modalen Fenster aktivieren", + "description-column-filter": "Aktiviert/deaktiviert Feldfilter in der Tabelle", + "description-count-rows": "Eine einzelne Anzahl für alle Datenzeilen anzeigen", + "description-fields": "Wählen Sie die Felder aus, die berechnet werden sollen", + "description-min-column-width": "Die Mindestbreite für die automatische Größenänderung der Spalte", "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Alternativer Text, der angezeigt wird, wenn ein Bild nicht angezeigt werden kann, oder für Nutzer, die einen Screenreader verwenden", @@ -11903,61 +12487,61 @@ "label-alt-text": "Alt-Text", "label-title-text": "Titeltext" }, - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-paginations": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "name-wrap-header-text": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "name-calculation": "Berechnung", + "name-cell-height": "Zellenhöhe", + "name-cell-type": "Zellentyp", + "name-cell-value-inspect": "Zellenwert prüfen", + "name-column-alignment": "Spaltenausrichtung", + "name-column-filter": "Spaltenfilter", + "name-column-width": "Spaltenbreite", + "name-count-rows": "Zeilen zählen", + "name-enable-paginations": "Seitennummerierung aktivieren", + "name-fields": "Felder", + "name-hide-in-table": "In Tabelle ausblenden", + "name-min-column-width": "Minimale Spaltenbreite", + "name-show-table-footer": "Tabellenfußzeile anzeigen", + "name-show-table-header": "Tabellenüberschirft anzeigen", + "name-wrap-header-text": "Kopfzeilentext umbrechen", + "placeholder-column-width": "auto", + "placeholder-fields": "Alle numerischen Felder" }, "table-new": { - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Zellenoptionen", + "category-table": "Tabelle", + "category-table-footer": "Tabellenfußzeile", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Groß", + "label-medium": "Mittel", + "label-small": "Klein" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Auto", + "label-center": "Mitte", + "label-left": "Links", + "label-right": "Rechts" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-pagination": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "description-calculation": "Wählen Sie eine Reduktionsfunktion/Berechnung", + "description-cell-value-inspect": "Zellwertprüfung in einem modalen Fenster aktivieren", + "description-column-filter": "Aktiviert/deaktiviert Feldfilter in der Tabelle", + "description-count-rows": "Eine einzelne Anzahl für alle Datenzeilen anzeigen", + "description-fields": "Wählen Sie die Felder aus, die berechnet werden sollen", + "description-min-column-width": "Die Mindestbreite für die automatische Größenänderung der Spalte", + "name-calculation": "Berechnung", + "name-cell-height": "Zellenhöhe", + "name-cell-type": "Zellentyp", + "name-cell-value-inspect": "Zellenwert prüfen", + "name-column-alignment": "Spaltenausrichtung", + "name-column-filter": "Spaltenfilter", + "name-column-width": "Spaltenbreite", + "name-count-rows": "Zeilen zählen", + "name-enable-pagination": "Seitennummerierung aktivieren", + "name-fields": "Felder", + "name-hide-in-table": "In Tabelle ausblenden", + "name-min-column-width": "Minimale Spaltenbreite", + "name-show-table-footer": "Tabellenfußzeile anzeigen", + "name-show-table-header": "Tabellenüberschirft anzeigen", + "placeholder-column-width": "auto", + "placeholder-fields": "Alle numerischen Felder" }, "tag-filter": { "clear-button": "Tags löschen", @@ -12042,17 +12626,17 @@ } }, "text": { - "category-text": "", + "category-text": "Text", "mode-options": { - "label-code": "", - "label-html": "", - "label-markdown": "" + "label-code": "Code", + "label-html": "HTML", + "label-markdown": "Markdown" }, - "name-content": "", - "name-language": "", - "name-mode": "", - "name-show-line-numbers": "", - "name-show-mini-map": "" + "name-content": "Inhalt", + "name-language": "Sprache", + "name-mode": "Modus", + "name-show-line-numbers": "Zeilennummern anzeigen", + "name-show-mini-map": "Minikarte anzeigen" }, "theme-preview": { "breadcrumbs": { @@ -12100,7 +12684,7 @@ "apply-button": "Zeitbereich anwenden", "default-error": "Bitte geben Sie ein vergangenes Datum oder „{{now}}“ ein", "fiscal-year": "Geschäftsjahr: {{from}}–{{to}}", - "from-input": "", + "from-input": "Ab", "open-input-calendar": "Kalender öffnen", "range-error": "„Von“ darf nicht nach „Bis“ sein", "to-input": "Bis" @@ -12131,8 +12715,8 @@ }, "timeline": { "missing-field": { - "all": "", - "time": "" + "all": "Keine grafischen Felder", + "time": "Daten haben kein Zeitfeld" } }, "timeseries": { @@ -12150,54 +12734,54 @@ "tooltip-delete": "Löschen", "tooltip-edit": "Bearbeiten" }, - "category-axis": "", + "category-axis": "Achse", "config": { "get-graph-field-config": { - "aria-label-bar-width-factor": "", - "aria-label-fill-opacity": "", - "aria-label-line-width": "", - "aria-label-point-size": "", - "category-graph-styles": "", - "category-thresholds": "", - "name-bar-alignment": "", - "name-bar-width-factor": "", - "name-connect-nulls": "", - "name-disconnect-values": "", - "name-fill-below-to": "", - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-interpolation": "", - "name-line-style": "", - "name-line-width": "", - "name-point-size": "", - "name-show-points": "", - "name-show-thresholds": "", - "name-style": "", - "name-transform": "", + "aria-label-bar-width-factor": "Balkenbreitenfaktor", + "aria-label-fill-opacity": "Fülldeckkraft", + "aria-label-line-width": "Linienbreite", + "aria-label-point-size": "Punktgröße", + "category-graph-styles": "Diagrammstile", + "category-thresholds": "Schwellenwerte", + "name-bar-alignment": "Balkenausrichtung", + "name-bar-width-factor": "Balkenbreitenfaktor", + "name-connect-nulls": "Nullwerte verbinden", + "name-disconnect-values": "Werte trennen", + "name-fill-below-to": "Unten ausfüllen bis", + "name-fill-opacity": "Fülldeckkraft", + "name-gradient-mode": "Farbverlaufsmodus", + "name-line-interpolation": "Linieninterpolation", + "name-line-style": "Linienstil", + "name-line-width": "Linienbreite", + "name-point-size": "Punktgröße", + "name-show-points": "Punkte zeigen", + "name-show-thresholds": "Schwellenwerte anzeigen", + "name-style": "Stil", + "name-transform": "Transformieren", "transform-options": { - "description-constant": "", - "description-style": "", - "label-constant": "", - "label-style": "" + "description-constant": "Der erste Wert wird als konstante Linie angezeigt", + "description-style": "Ergebnisse auf der Y-Achse auf negative Werte umkehren", + "label-constant": "Konstant", + "label-style": "Negativ Y" } } }, "insert-nulls-editor": { "disconnect-options": { - "label-never": "", - "label-threshold": "" + "label-never": "Nie", + "label-threshold": "Schwellenwert" } }, "line-style-editor": { "line-fill-options": { - "label-dash": "", - "label-dots": "", - "label-solid": "" + "label-dash": "Strich", + "label-dots": "Punkte", + "label-solid": "Durchgezogen" }, "title-the-input-expects-a-segment-list": "Die Eingabe verlangt eine Segmentliste", "tooltip-help": "Hilfe" }, - "name-time-zone": "", + "name-time-zone": "Zeitzone", "nulls-threshold-input": { "placeholder-never": "Nie" }, @@ -12207,9 +12791,9 @@ }, "span-nulls-editor": { "gaps-options": { - "label-always": "", - "label-never": "", - "label-threshold": "" + "label-always": "Immer", + "label-never": "Nie", + "label-threshold": "Schwellenwert" } }, "timezones-editor": { @@ -12218,17 +12802,17 @@ } }, "traces": { - "category-span-filters": "", - "name-critical-path-only": "", - "name-find-in-trace": "", - "name-max-duration": "", - "name-min-duration": "", - "name-service-name": "", - "name-service-name-operator": "", - "name-show-matches-only": "", - "name-span-name": "", - "name-span-name-operator": "", - "name-tags": "", + "category-span-filters": "Spannenfilter", + "name-critical-path-only": "Nur kritischen Pfad anzeigen", + "name-find-in-trace": "In Trace suchen", + "name-max-duration": "Höchstdauer", + "name-min-duration": "Mindestdauer", + "name-service-name": "Dienstname", + "name-service-name-operator": "Dienstnamen-Operator", + "name-show-matches-only": "Nur Übereinstimmungen anzeigen", + "name-span-name": "Spannen-Name", + "name-span-name-operator": "Spannen-Namen-Operator", + "name-tags": "Tags", "traces-panel": { "no-data-found-in-response": "Keine Daten in der Antwort gefunden" } @@ -12243,20 +12827,20 @@ "axis-editor": { "log-mode-options": { "description": { - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-size": "Buckets je nach Größe aufteilen" }, "label": { - "split": "" + "split": "Aufteilen" } }, "mode-options": { "description": { - "split-the-buckets-based-on-count": "", - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-count": "Buckets je nach Anzahl aufteilen", + "split-the-buckets-based-on-size": "Buckets je nach Größe aufteilen" }, "label": { - "count": "", - "size": "" + "count": "Anzahl", + "size": "Größe" } }, "placeholder-auto": "Auto" @@ -12288,9 +12872,9 @@ }, "calculate-heatmap": { "add-heatmap-calculation-options": { - "name-x-bucket": "", - "name-y-bucket": "", - "name-y-bucket-scale": "" + "name-x-bucket": "X-Bucket", + "name-y-bucket": "Y-Bucket", + "name-y-bucket-scale": "Y-Bucket-Skala" } }, "concatenate-transformer-editor": { @@ -12351,8 +12935,8 @@ "tooltip-enable": "Aktivieren" }, "draggable-sort-order": { - "asc": "", - "desc": "", + "asc": "AUF", + "desc": "AB", "off": "Aus" }, "editors": { @@ -12751,9 +13335,9 @@ "description": { "reorder-hide-or-rename-fields": "" }, - "field-order": "", + "field-order": "Feldreihenfolge", "field-order-auto": "Auto", - "field-order-manual": "", + "field-order-manual": "Manuell", "first-frame-warning": "Felder organisieren funktioniert nur mit einem einzelnen Frame. Überlegen Sie, zunächst eine Join-Transformation anzuwenden oder die Eingabe zu filtern.", "name": { "organize-fields": "" @@ -12880,7 +13464,7 @@ "polynomial": "Polynom" } }, - "regression": "", + "regression": "Regression", "tooltip-number-of-xy-points-to-predict": "Anzahl der zu prognostizierenden X-, Y-Punkte" }, "rename-by-regex-transformer": { @@ -13033,10 +13617,10 @@ } }, "trend": { - "category-x-axis": "", - "description-x-field": "", - "name-x-field": "", - "placeholder-x-field": "" + "category-x-axis": "X-Achse", + "description-x-field": "Ein steigender numerischer Wert", + "name-x-field": "X-Feld", + "placeholder-x-field": "Erster numerischer Wert" }, "upgrade-box": { "discovery-text": "Sie haben eine Pro-Funktion entdeckt!", @@ -13299,20 +13883,20 @@ } }, "xychart": { - "category-xychart": "", - "name-fill-opacity": "", - "name-line-style": "", - "name-line-width": "", - "name-max-point-size": "", - "name-min-point-size": "", - "name-point-shape": "", - "name-point-size": "", - "name-point-stroke-width": "", - "name-series-mapping": "", - "name-show": "", + "category-xychart": "XY-Diagramm", + "name-fill-opacity": "Fülldeckkraft", + "name-line-style": "Linienstil", + "name-line-width": "Linienbreite", + "name-max-point-size": "Max. Punktgröße", + "name-min-point-size": "Min. Punktgröße", + "name-point-shape": "Punktform", + "name-point-size": "Punktgröße", + "name-point-stroke-width": "Punktstrichbreite", + "name-series-mapping": "Reihenzuordnung", + "name-show": "Anzeigen", "point-shape-options": { - "label-circle": "", - "label-square": "" + "label-circle": "Kreis", + "label-square": "Quadrat" }, "series-editor": { "add-series": "Reihe hinzufügen", @@ -13324,18 +13908,18 @@ "label-y-field": "Y-Feld", "placeholder-all-frames": "Alle Frames", "placeholder-select-frame": "Frame auswählen", - "placeholder-x-field": "", - "placeholder-y-field": "", + "placeholder-x-field": "Erstes Zahlen- oder Zeitfeld in jedem Frame", + "placeholder-y-field": "Verbleibende Zahlenfelder in jedem Frame", "tooltip-delete-series": "Reihe löschen" }, "series-mapping-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Auto", + "label-manual": "Manuell" }, "show-options": { - "label-both": "", - "label-lines": "", - "label-points": "" + "label-both": "Beides", + "label-lines": "Linien", + "label-points": "Punkte" } } } \ No newline at end of file diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index bd097338400..2fad1ff4fb6 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "AI Alert Rule Button failed to load", + "feedback-button": "AI Feedback Button failed to load", "improve-annotations-button": "AI Improve Annotations Button failed to load", "improve-labels-button": "AI Improve Labels Button failed to load", "template-button": "AI Template Button failed to load", @@ -1551,7 +1552,8 @@ "title": "Edit evaluation group" }, "group-loader": { - "group-load-failed": "Failed to load rules from group {{ groupName }} in {{ namespaceName }}" + "group-load-failed": "Failed to load rules from group {{ groupName }} in {{ namespaceName }}", + "no-rules": "No rules found in this group" }, "group-status": { "content-the-group-is-being-deleted": "The group is being deleted" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configure", - "draft-new-rule": "Draft a new rule", "ds-error": { "title": "Cannot load rules for this datasource" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "New alert rule", "new-datasource-recording-rule": "New Data source recording rule", "new-grafana-recording-rule": "New Grafana recording rule", + "new-rule-for-export": "New alert rule for export", "pagination": { "next-page": "Show more…" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Refresh preview", + "empty-state": "Add template content to see preview", "label-preview": "Preview", "refresh": "Refresh" }, @@ -3359,6 +3362,9 @@ "text-badge-enabled": "Enabled", "text-badge-not-enabled": "Not enabled" }, + "scim-banner": { + "message": "SCIM is currently in development and not recommended for production use. Please use with caution and expect potential changes." + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "The .well-known/openid-configuration endpoint for your IdP", "title-open-id-connect-discovery-url": "OpenID Connect Discovery URL" @@ -3476,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "Bulk Delete Provisioned Resources", - "bulk-delete-provisioned-resources-not-implemented": "Bulk delete for provisioned resources is not implemented yet.", "cancel-button": "Cancel", "cannot-move-folders": "Folders cannot be moved", "confirmation-text": "Delete", @@ -3509,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "This folder is empty" }, + "bulk-action-resources-form": { + "failed-alert_one": "{{count}} items failed", + "failed-alert_other": "{{count}} items failed" + }, + "bulk-delete-resources-form": { + "button-cancel": "Cancel", + "button-delete": "Delete", + "button-deleting": "Deleting...", + "button-done": "Done", + "delete-warning": "This will delete selected folders and their descendants. In total, this will affect:", + "error-path-not-found": "Path not found", + "progress-title": "Success", + "success-message": "All resources have been deleted successfully." + }, + "bulk-move-resources-form": { + "deleting": "Deleting:", + "progress": "Progress: {{current}} of {{total}}" + }, "counts": { "alertRule_one": "{{count}} alert rule", "alertRule_other": "{{count}} alert rule", @@ -3559,6 +3582,7 @@ "empty-message": "No folders found", "error-title": "Error loading folders", "non-folder-item": "Non-folder {{itemKind}} {{itemUID}}", + "root-title": "Dashboards", "search-placeholder": "Search folders", "unknown-error": "Unknown error" }, @@ -4542,6 +4566,10 @@ }, "edit-actions": { "add": "Add {{typeName}}", + "add-conditional-rule": "Add {{ruleDescription}} rule", + "edit-query-result-rule": "Change query result rule", + "edit-template-variable-rule": "Change template variable rule", + "edit-time-range-rule": "Change time range rule", "move": "Move {{typeName}}", "panel-background": "Change panel background", "panel-description": "Change panel description", @@ -4556,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "element": "Element", "local-variable": "Local variable", "multiple-elements": "Multiple elements", "multiple-elements-delete-text": "Are you sure you want to delete these elements?", @@ -5765,7 +5794,6 @@ "move-action": "Move dashboard", "move-read-only-message": "This dashboard cannot be moved directly from Grafana because the repository is read-only. To move this dashboard, please move the file in your Git repository.", "moving": "Moving...", - "partial-failure-warning": "Dashboard was created at new location but could not be deleted from original location. Please manually remove the old file.", "target-path-label": "Target path", "title-this-repository-is-read-only": "This repository is read only" }, @@ -5931,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Someone else has updated this dashboard", "would-still-dashboard": "Would you still like to save this dashboard?" }, - "schema-v2-library-panels-export": "The dynamic dashboard functionality is experimental, and has not full feature parity with current dashboards behaviour. It is based on a new schema format, that does not support library panels. This means that when exporting the dashboard to use it in another instance, we will not include library panels. We intend to support them as we progress in the feature <2>life cycle.", - "schema-v2-library-panels-export-title": "Dashboard Schema V2 does not support exporting library panels to be used in another instance yet", + "schema-v2-library-panels-export": "Due to limitations in the new dashboard schema (V2), library panels will be converted to regular panels with embedded content during external export.", + "schema-v2-library-panels-export-title": "Library panels will be converted to regular panels", "title-dashboard-drastically-changed": "Dashboard irreversibly changed" }, "save-dashboard-form-common-options": { @@ -7136,6 +7164,7 @@ "start-time": "Start Time:" } }, + "share-span": "Share", "warnings": "Warnings" }, "span-filters": { @@ -7715,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "Feet/sec²", + "name-g-unit": "G unit", + "name-meters-sec": "Meters/sec²" + }, + "name": "Acceleration" + }, + "angle": { + "formats": { + "name-arc-minutes": "Arc Minutes", + "name-arc-seconds": "Arc Seconds", + "name-degrees": "Degrees (°)", + "name-gradian": "Gradian", + "name-radians": "Radians" + }, + "name": "Angle" + }, + "area": { + "formats": { + "name-acres": "Acres (ac)", + "name-hectares": "Hectares (ha)", + "name-square-feet": "Square Feet (ft²)", + "name-square-meters": "Square Meters (m²)", + "name-square-miles": "Square Miles (mi²)" + }, + "name": "Area" + }, + "boolean": { + "formats": { + "name-on-off": "On / Off", + "name-true-false": "True / False", + "name-yes-no": "Yes / No" + }, + "name": "Boolean" + }, + "computation": { + "formats": { + "name-eflops": "EFLOP/s", + "name-flops": "FLOP/s", + "name-gflops": "GFLOP/s", + "name-mflops": "MFLOP/s", + "name-pflops": "PFLOP/s", + "name-tflops": "TFLOP/s", + "name-yflops": "YFLOP/s", + "name-zflops": "ZFLOP/s" + }, + "name": "Computation" + }, + "concentration": { + "formats": { + "name-g-m3": "gram per cubic meter (g/m³)", + "name-g-nm3": "gram per normal cubic meter (g/Nm³)", + "name-mg-dl": "milligrams per decilitre (mg/dL)", + "name-mg-m3": "milligram per cubic meter (mg/m³)", + "name-mg-nm3": "milligram per normal cubic meter (mg/Nm³)", + "name-mmol-l": "millimoles per litre (mmol/L)", + "name-ng-m3": "nanogram per cubic meter (ng/m³)", + "name-ng-nm3": "nanogram per normal cubic meter (ng/Nm³)", + "name-ppb": "parts-per-billion (ppb)", + "name-ppm": "parts-per-million (ppm)", + "name-ug-m3": "microgram per cubic meter (μg/m³)", + "name-ug-nm3": "microgram per normal cubic meter (μg/Nm³)" + }, + "name": "Concentration" + }, + "currency": { + "formats": { + "name-bgn": "Bulgarian Lev (BGN)", + "name-brl": "Real (R$)", + "name-btc": "Bitcoin (฿)", + "name-chf": "Swiss franc (CHF)", + "name-czk": "Czech koruna (czk)", + "name-dkk": "Danish Krone (kr)", + "name-eur": "Euro (€)", + "name-gbp": "Pounds (£)", + "name-idr": "Indonesian Rupiah (Rp)", + "name-ils": "Israeli New Shekels (₪)", + "name-inr": "Indian Rupee (₹)", + "name-isk": "Icelandic Króna (kr)", + "name-jpy": "Yen (¥)", + "name-krw": "South Korean Won (₩)", + "name-mbtc": "Milli Bitcoin (฿)", + "name-myr": "Malaysian Ringgit (RM)", + "name-nok": "Norwegian Krone (kr)", + "name-php": "Philippine Peso (PHP)", + "name-pln": "Polish Złoty (PLN)", + "name-pyg": "Guaraní (₲)", + "name-rub": "Rubles (₽)", + "name-sek": "Swedish Krona (kr)", + "name-try": "Turkish Lira (₺)", + "name-uah": "Hryvnias (₴)", + "name-ubtc": "Micro Bitcoin (฿)", + "name-usd": "Dollars ($)", + "name-uyu": "Uruguay Peso (UYU)", + "name-vnd": "Vietnamese Dong (VND)", + "name-xpf": "CFP franc (XPF)", + "name-zar": "South African Rand (R)" + }, + "name": "Currency" + }, + "data": { + "formats": { + "name-bits-iec": "bits(IEC)", + "name-bits-si": "bits(SI)", + "name-bytes-iec": "bytes(IEC)", + "name-bytes-si": "bytes(SI)", + "name-gibibytes": "gibibytes", + "name-gigabytes": "gigabytes", + "name-kibibytes": "kibibytes", + "name-kilobytes": "kilobytes", + "name-mebibytes": "mebibytes", + "name-megabytes": "megabytes", + "name-pebibytes": "pebibytes", + "name-petabytes": "petabytes", + "name-tebibytes": "tebibytes", + "name-terabytes": "terabytes" + }, + "name": "Data" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "bits/sec(IEC)", + "name-bits-sec-si": "bits/sec(SI)", + "name-bytes-sec-iec": "bytes/sec(IEC)", + "name-bytes-sec-si": "bytes/sec(SI)", + "name-gibibits-sec": "gibibits/sec", + "name-gibibytes-sec": "gibibytes/sec", + "name-gigabits-sec": "gigabits/sec", + "name-gigabytes-sec": "gigabytes/sec", + "name-kibibits-sec": "kibibits/sec", + "name-kibibytes-sec": "kibibytes/sec", + "name-kilobits-sec": "kilobits/sec", + "name-kilobytes-sec": "kilobytes/sec", + "name-mebibits-sec": "mebibits/sec", + "name-mebibytes-sec": "mebibytes/sec", + "name-megabits-sec": "megabits/sec", + "name-megabytes-sec": "megabytes/sec", + "name-packets-sec": "packets/sec", + "name-pebibits-sec": "pebibits/sec", + "name-pebibytes-sec": "pebibytes/sec", + "name-petabits-sec": "petabits/sec", + "name-petabytes-sec": "petabytes/sec", + "name-tebibits-sec": "tebibits/sec", + "name-tebibytes-sec": "tebibytes/sec", + "name-terabits-sec": "terabits/sec", + "name-terabytes-sec": "terabytes/sec" + }, + "name": "Data rate" + }, + "date-time": { + "formats": { + "name-datetime-default": "Datetime default", + "name-datetime-iso": "Datetime ISO", + "name-datetime-iso-no-date": "Datetime ISO (No date if today)", + "name-datetime-local": "Datetime local", + "name-datetime-local-no-date": "Datetime local (No date if today)", + "name-datetime-us": "Datetime US", + "name-datetime-us-no-date": "Datetime US (No date if today)", + "name-from-now": "From Now" + }, + "name": "Date & time" + }, + "energy": { + "formats": { + "name-ampere": "Ampere (A)", + "name-ampere-hour": "Ampere-hour (Ah)", + "name-decibel-milliwatt": "Decibel-milliwatt (dBm)", + "name-electron-volt": "Electron volt (eV)", + "name-farad": "Farad (F)", + "name-femtofarad": "Femtofarad (fF)", + "name-gigawatt": "Gigawatt (GW)", + "name-henry": "Henry (H)", + "name-joule": "Joule (J)", + "name-kiloampere": "Kiloampere (kA)", + "name-kiloampere-hour": "Kiloampere-hour (kAh)", + "name-kiloohm": "Kiloohm (kΩ)", + "name-kilovolt": "Kilovolt (kV)", + "name-kilovolt-ampere": "Kilovolt-Ampere (kVA)", + "name-kilovolt-ampere-reactive": "Kilovolt-Ampere reactive (kVAr)", + "name-kilowatt": "Kilowatt (kW)", + "name-kilowatt-hour": "Kilowatt-hour (kWh)", + "name-kilowatt-min": "Kilowatt-min (kWm)", + "name-lumens": "Lumens (Lm)", + "name-megaohm": "Megaohm (MΩ)", + "name-megawatt": "Megawatt (MW)", + "name-megawatt-hour": "Megawatt-hour (MWh)", + "name-microfarad": "Microfarad (µF)", + "name-microhenry": "Microhenry (µH)", + "name-milliampere": "Milliampere (mA)", + "name-milliampere-hour": "Milliampere-hour (mAh)", + "name-millihenry": "Millihenry (mH)", + "name-milliohm": "Milliohm (mΩ)", + "name-millivolt": "Millivolt (mV)", + "name-milliwatt": "Milliwatt (mW)", + "name-nanofarad": "Nanofarad (nF)", + "name-ohm": "Ohm (Ω)", + "name-picofarad": "Picofarad (pF)", + "name-volt": "Volt (V)", + "name-volt-ampere": "Volt-Ampere (VA)", + "name-volt-ampere-reactive": "Volt-Ampere reactive (VAr)", + "name-watt": "Watt (W)", + "name-watt-hour": "Watt-hour (Wh)", + "name-watt-hour-per-kg": "Watt-hour per Kilogram (Wh/kg)", + "name-watt-square-meter": "Watt per square meter (W/m²)" + }, + "name": "Energy" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "Cubic feet/min (cfm)", + "name-cubic-feet-sec": "Cubic feet/sec (cfs)", + "name-cubic-meters-sec": "Cubic meters/sec (cms)", + "name-gallons-min": "Gallons/min (gpm)", + "name-litre-hour": "Litre/hour", + "name-litre-min": "Litre/min (L/min)", + "name-lux": "Lux (lx)", + "name-millilitre-min": "milliLitre/min (mL/min)" + }, + "name": "Flow" + }, + "force": { + "formats": { + "name-kilonewton-meters": "Kilonewton-meters (kNm)", + "name-kilonewtons": "Kilonewtons (kN)", + "name-newton-meters": "Newton-meters (Nm)", + "name-newtons": "Newtons (N)" + }, + "name": "Force" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "exahashes/sec", + "name-gigahashes-sec": "gigahashes/sec", + "name-hashes-sec": "hashes/sec", + "name-kilohashes-sec": "kilohashes/sec", + "name-megahashes-sec": "megahashes/sec", + "name-petahashes-sec": "petahashes/sec", + "name-terahashes-sec": "terahashes/sec" + }, + "name": "Hash rate" + }, + "length": { + "formats": { + "name-feet": "feet (ft)", + "name-inch": "inch (in)", + "name-kilometer": "kilometer (km)", + "name-meter": "meter (m)", + "name-mile": "mile (mi)", + "name-millimeter": "millimeter (mm)" + }, + "name": "Length" + }, + "mass": { + "formats": { + "name-gram": "gram (g)", + "name-kilogram": "kilogram (kg)", + "name-metric-ton": "metric ton (t)", + "name-milligram": "milligram (mg)", + "name-pound": "pound (lb)" + }, + "name": "Mass" + }, + "misc": { + "formats": { + "name-candala": "Candela (cd)", + "name-decibel": "Decibel", + "name-hexadecimal": "Hexadecimal", + "name-hexadecimal-0x": "Hexadecimal (0x)", + "name-humidity": "Humidity (%H)", + "name-locale": "Locale format", + "name-number": "Number", + "name-percent-1": "Percent (0.0-1.0)", + "name-percent-100": "Percent (0-100)", + "name-pixels": "Pixels", + "name-scientific": "Scientific notation", + "name-short": "short", + "name-si-short": "SI short", + "name-string": "String" + }, + "name": "Misc" + }, + "pressure": { + "formats": { + "name-bars": "Bars", + "name-hectopascals": "Hectopascals", + "name-inches-mercury": "Inches of mercury", + "name-kilobars": "Kilobars", + "name-kilopascals": "Kilopascals", + "name-millibars": "Millibars", + "name-pascals": "Pascals", + "name-psi": "PSI" + }, + "name": "Pressure" + }, + "radiation": { + "formats": { + "name-becquerel": "Becquerel (Bq)", + "name-curie": "curie (Ci)", + "name-exposure": "Exposure (C/kg)", + "name-gray": "Gray (Gy)", + "name-microsievert": "microSievert (µSv)", + "name-microsievert-hour": "microSievert/hour (µSv/h)", + "name-millisievert": "milliSievert (mSv)", + "name-millisievert-hour": "milliSievert/hour (mSv/h)", + "name-rad": "rad", + "name-rem": "rem", + "name-roentgen": "roentgen (R)", + "name-sievert": "Sievert (Sv)", + "name-sievert-hour": "Sievert/hour (Sv/h)" + }, + "name": "Radiation" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "Degrees per second (°/s)", + "name-gigahertz": "Gigahertz (GHz)", + "name-hertz": "Hertz (Hz)", + "name-kilohertz": "Kilohertz (kHz)", + "name-megahertz": "Megahertz (MHz)", + "name-radians-sec": "Radians per second (rad/s)", + "name-rpm": "Revolutions per minute (rpm)" + }, + "name": "Rotational Speed" + }, + "temperature": { + "formats": { + "name-celsius": "Celsius (°C)", + "name-fahrenheit": "Fahrenheit (°F)", + "name-kelvin": "Kelvin (K)" + }, + "name": "Temperature" + }, + "throughput": { + "formats": { + "name-counts-min": "counts/min (cpm)", + "name-counts-sec": "counts/sec (cps)", + "name-events-min": "events/min (epm)", + "name-events-sec": "events/sec (eps)", + "name-io-ops-sec": "I/O ops/sec (iops)", + "name-messages-min": "messages/min (mpm)", + "name-messages-sec": "messages/sec (mps)", + "name-ops-min": "ops/min (opm)", + "name-ops-sec": "ops/sec (ops)", + "name-reads-min": "reads/min (rpm)", + "name-reads-sec": "reads/sec (rps)", + "name-records-min": "records/min (rpm)", + "name-records-sec": "records/sec (rps)", + "name-requests-min": "requests/min (rpm)", + "name-requests-sec": "requests/sec (rps)", + "name-rows-min": "rows/min (rpm)", + "name-rows-sec": "rows/sec (rps)", + "name-writes-min": "writes/min (wpm)", + "name-writes-sec": "writes/sec (wps)" + }, + "name": "Throughput" + }, + "time": { + "formats": { + "name-clock-ms": "clock (ms)", + "name-clock-s": "clock (s)", + "name-days": "days (d)", + "name-duration-dhms": "duration (d hh:mm:ss)", + "name-duration-hms": "duration (hh:mm:ss)", + "name-duration-ms": "duration (ms)", + "name-duration-s": "duration (s)", + "name-hertz": "Hertz (1/s)", + "name-hours": "hours (h)", + "name-microseconds": "microseconds (µs)", + "name-milliseconds": "milliseconds (ms)", + "name-minutes": "minutes (m)", + "name-nanoseconds": "nanoseconds (ns)", + "name-seconds": "seconds (s)", + "name-timeticks": "Timeticks (s/100)" + }, + "name": "Time" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "kilometers/hour (km/h)", + "name-knot": "knot (kn)", + "name-meters-second": "meters/second (m/s)", + "name-miles-hour": "miles/hour (mph)" + }, + "name": "Velocity" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "cubic decimeter", + "name-cubic-meter": "cubic meter", + "name-gallons": "gallons", + "name-litre": "litre (L)", + "name-millilitre": "millilitre (mL)", + "name-normal-cubic-meter": "Normal cubic meter" + }, + "name": "Volume" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7954,6 +8386,60 @@ "previous-label": "Previous month", "select-placeholder": "Select date/time" }, + "date-time-pickers": { + "month-options": { + "label-april": "April", + "label-august": "August", + "label-december": "December", + "label-february": "February", + "label-january": "January", + "label-july": "July", + "label-june": "June", + "label-march": "March", + "label-may": "May", + "label-november": "November", + "label-october": "October", + "label-september": "September" + }, + "quick-options": { + "day-before-yesterday": "Day before yesterday", + "last-1-hour": "Last 1 hour", + "last-1-year": "Last 1 year", + "last-12-hours": "Last 12 hours", + "last-15-mins": "Last 15 minutes", + "last-2-days": "Last 2 days", + "last-2-years": "Last 2 years", + "last-24-hours": "Last 24 hours", + "last-3-hours": "Last 3 hours", + "last-30-days": "Last 30 days", + "last-30-mins": "Last 30 minutes", + "last-5-mins": "Last 5 minutes", + "last-5-years": "Last 5 years", + "last-6-hours": "Last 6 hours", + "last-6-months": "Last 6 months", + "last-7-days": "Last 7 days", + "last-90-days": "Last 90 days", + "previous-fiscal-quarter": "Previous fiscal quarter", + "previous-fiscal-year": "Previous fiscal year", + "previous-month": "Previous month", + "previous-week": "Previous week", + "previous-year": "Previous year", + "this-day-last-week": "This day last week", + "this-fiscal-quarter": "This fiscal quarter", + "this-fiscal-quarter-so-far": "This fiscal quarter so far", + "this-fiscal-year": "This fiscal year", + "this-fiscal-year-so-far": "This fiscal year so far", + "this-month": "This month", + "this-month-so-far": "This month so far", + "this-week": "This week", + "this-week-so-far": "This week so far", + "this-year": "This year", + "this-year-so-far": "This year so far", + "today": "Today", + "today-so-far": "Today so far", + "yesterday": "Yesterday" + } + }, "drawer": { "close": "Close" }, @@ -8001,6 +8487,20 @@ "field-name-picker": { "placeholder": "Select field" }, + "get-all-field-type-icon-options": { + "label-boolean": "Boolean", + "label-enum": "Enum", + "label-number": "Number", + "label-other": "Other", + "label-string": "String", + "label-time": "Time", + "label-traces": "Traces" + }, + "get-list-of-query-ref-ids": { + "description": "Frames ({{framesCount}}): {{framesNames}}", + "label": "Query: {{refId}}", + "label-missing-ref-id": "Query: (missing refId)" + }, "name-field-name-by-regex-matcher": "Fields with name matching regex", "name-fields-by-query": "Fields returned by query", "name-fields-with-name": "Fields with name", @@ -8105,7 +8605,10 @@ "no-data": "No data" }, "no-values-label": "No values", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} of {{numRows}} rows" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} of {{numRows}} rows", + "sparkline": { + "no-data": "no data" + } }, "tags": { "list-label": "Tags" @@ -8757,9 +9260,9 @@ "type": { "loki": { "indexed-label_one": "Indexed label", - "indexed-label_other": "Indexed labels", + "indexed-label_other": "Indexed label", "parsedl-label_one": "Parsed field", - "parsedl-label_other": "Parsed fields", + "parsedl-label_other": "Parsed field", "structured-metadata_one": "Structured metadata", "structured-metadata_other": "Structured metadata" } @@ -8838,8 +9341,11 @@ "move-displayed-field-down": "Move down", "move-displayed-field-up": "Move up", "no-details": "No fields to display.", + "open-assistant": "Explain this log line in Assistant", "pin-line": "Pin log", "remove-displayed-field": "Remove field", + "remove-log": "Remove log", + "scroll-to-logline": "Scroll to log line", "search": { "no-results": "No results to display." }, @@ -8854,6 +9360,9 @@ "copy-log": "Copy log line", "hide-details": "Show log details", "icon-label": "Log menu", + "log-line": "Log line", + "log-line-explainer": "Explain this log line in a concise way", + "open-assistant": "Explain this log line in Assistant", "pin-to-outline": "Pin log", "show-context": "Show context", "show-details": "Hide log details", @@ -9687,7 +10196,9 @@ "aria-label": "Help" }, "invite-user": { - "invite-new-member-button": "Invite new member" + "invite-button": "Invite", + "invite-new-member-button": "Invite new member", + "invite-tooltip": "Invite user" }, "item": { "add-bookmark": "Add to Bookmarks", @@ -10445,24 +10956,50 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { - "not-saved": "The value is not yet saved in the Grafana database", - "open-pull-request-in-git-hub": "Open pull request in GitHub", - "view-pull-request-in-git-hub": "View pull request in GitHub" + "behind-branch-text": "This resource is behind the branch in {{repoType}}.", + "not-saved": "The rest of Grafana users in your organization will still see the current version saved to configured default branch until this branch is merged", + "open-in-repo-button": "Open in {{repoType}}", + "open-pull-request-in-repo": "Open pull request in {{repoType}}", + "view-in-repo-button": "View it in {{repoType}} to see the latest changes.", + "view-pull-request-in-repo": "View pull request in {{repoType}}" }, - "title-created-branch-git-hub": "A new resource has been created in a branch in GitHub.", - "title-loaded-pull-request-git-hub": "This resource is loaded from a pull request in GitHub." + "title-created-branch-in-repo": "A new resource has been created in a branch in {{repoType}}.", + "title-loaded-pull-request-in-repo": "This resource is loaded from the branch you just created in {{repoType}} and it is only visible to you" }, "provisioning": { "banner": { "message": "This feature is currently under active development. For the best experience and latest improvements, we recommend using the <2>nightly build of Grafana." }, + "bitbucket": { + "branch-description": "The branch to use for provisioning", + "branch-label": "Branch", + "branch-required": "Branch is required", + "path-description": "Optional subdirectory path within the repository", + "path-label": "Path", + "pr-workflow-description": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.", + "pr-workflow-label": "Enable pull request option when saving", + "token-description": "Bitbucket App Password with repository permissions", + "token-label": "App Password", + "token-required": "Bitbucket token is required", + "token-user-description": "The username that will be used to access the repository with the app password", + "token-user-label": "Username", + "token-user-required": "Username is required", + "url-description": "The Bitbucket repository URL", + "url-label": "Repository URL", + "url-pattern": "Must be a valid Bitbucket repository URL", + "url-required": "Repository URL is required" + }, "bootstrap-step": { + "dashboards-count_one": "{{count}} dashboard", + "dashboards-count_other": "{{count}} dashboard", "description-clear-repository-connection": "Add a clear name for this repository connection", "empty": "Empty", "error-field-required": "This field is required.", "ext-storage": "External storage", "files-count_one": "{{count}} files", "files-count_other": "{{count}} files", + "folders-count_one": "{{count}} folder", + "folders-count_other": "{{count}} folder", "grafana": "Grafana instance", "label-display-name": "Display name", "placeholder-my-repository-connection": "My repository connection", @@ -10477,36 +11014,20 @@ "button-save": "Save", "button-saving": "Saving...", "description-enabled": "Once automatic pulling is enabled, the target cannot be changed.", - "description-path": "Path to a subdirectory in the Git repository", "description-read-only": "Resources can't be modified through Grafana.", - "description-repository-url": "Enter the GitHub repository URL", "description-title": "A human-readable name for the config", "error-required": "This field is required.", "error-save-repository": "Failed to save repository settings", - "error-valid-github-url": "Please enter a valid GitHub repository URL", "label-automatic-pulling": "Automatic pulling", - "label-branch": "Branch", "label-enabled": "Enabled", - "label-github-token": "GitHub token", "label-interval-seconds": "Interval (seconds)", - "label-local-path": "Local path", - "label-path": "Path", - "label-pr-workflow": "Enable pull request option when saving", "label-repository-type": "Repository type", - "label-repository-url": "Repository URL", "label-target": "Target", "label-title": "Title", "option-entire-instance": "Entire instance", - "option-github": "GitHub", - "option-local": "Local", "option-managed-folder": "Managed folder", - "placeholder-branch": "main", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "My config", - "placeholder-select-repository-type": "Select repository type" + "placeholder-my-config": "My config" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configure webhooks to get instant updates in Grafana as soon as changes are committed. Review and approve changes using pull requests before they go live.", @@ -10520,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configure", - "configure-file": "Configure file provisioning", - "configure-git-sync": "Configure Git Sync", "repository-limit-info-alert": "Repository limit reached ({{count}})" }, - "connect-step": { - "description-branch": "Branch to use for the GitHub repository", - "description-github-path": "This is the path to a subdirectory in your GitHub repository where dashboards will be stored and provisioned from", - "description-paste-your-git-hub-personal-access-token": "Paste your GitHub personal access token", - "description-repository-url": "Paste the URL of your GitHub repository", - "error-field-required": "This field is required.", - "error-invalid-github-url": "Please enter a valid GitHub repository URL", - "label-access-token": "GitHub access token", - "label-branch": "Branch name", - "label-local-path": "Local path", - "label-path": "Path to subdirectory in repository", - "label-repository-url": "GitHub repository URL", - "placeholder-branch": "main", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Delete", "confirm-delete-repository": "Are you sure you want to delete the repository config?", @@ -10572,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Instant updates and pull requests with webhooks.", "title-visual-previews-in-pull-requests": "Visual previews in pull requests with image rendering" }, + "error-title-default": "Error", "expanded-row": { "job-specification": "Job Specification", - "summary": "Summary", - "title-error": "Error" + "summary": "Summary" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Set up required feature toggles" }, - "manage-dashboards-provision-updates-automatically": "Manage dashboards as code in GitHub and provision updates automatically", - "manage-your-dashboards-with-remote-provisioning": "Get started with GitSync", + "manage-dashboards-provision-updates-automatically": "Manage dashboards as code in Git and provision updates automatically", + "manage-your-dashboards-with-remote-provisioning": "Get started with Git Sync", "store-dashboards-in-version-controlled-storage": "Store dashboards in version-controlled storage for better organization and history tracking" }, "file-history-page": { @@ -10603,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Adds an image preview of dashboard changes in pull requests. Images of your Grafana dashboards will be shared in your Git repository and visible to anyone with repository access.", + "description-generate-dashboard-previews": "Create preview links for pull requests", "description-image-rendering": "Requires image rendering. <2>Set up image rendering", - "description-often-shall-instance-updates-git-hub": "How often shall the instance pull updates from GitHub?", - "description-pr-enable-description": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.", + "description-preview-requirements": "(requires <2>image rendering and public access enabled)", "description-read-only": "Resources can't be modified through Grafana.", - "description-webhooks-enable": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.", + "description-sync-interval": "How often to sync changes from the repository", + "error-sync-interval-required": "Sync interval is required", "label-enable-previews": "Enable dashboard previews in pull requests", - "label-pr-workflow": "Enable pull request option when saving", + "label-generate-dashboard-previews": "Generate Dashboard Previews", "label-read-only": "Read only", - "label-update-instance-interval-seconds": "Update instance interval (seconds)", - "placeholder": "60", - "text-setup-later": "You can always set this up later", - "title-enhance-github": "Enhance your GitHub experience" + "label-sync-interval": "Sync Interval (seconds)" }, "folder-repository-list": { "no-results-matching-your-query": "No results matching your query", @@ -10645,6 +11145,57 @@ "header": "Provisioning", "subtitle-provisioning-feature": "View and manage your provisioning connections" }, + "git": { + "branch-description": "The branch to use for provisioning", + "branch-label": "Branch", + "branch-required": "Branch is required", + "path-description": "Optional subdirectory path within the repository", + "path-label": "Path", + "pr-workflow-description": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.", + "pr-workflow-label": "Enable pull request option when saving", + "token-description": "Git repository access token or password", + "token-label": "Access Token", + "token-required": "Git token is required", + "token-user-description": "The username that will be used to access the repository with the access token", + "token-user-label": "Username", + "token-user-required": "Username is required", + "url-description": "The Git repository URL", + "url-label": "Repository URL", + "url-pattern": "Must be a valid Git repository URL", + "url-required": "Repository URL is required" + }, + "github": { + "branch-description": "The branch to use for provisioning", + "branch-label": "Branch", + "branch-required": "Branch is required", + "path-description": "Optional subdirectory path within the repository", + "path-label": "Path", + "pr-workflow-description": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.", + "pr-workflow-label": "Enable pull request option when saving", + "token-description": "GitHub Personal Access Token with repository permissions", + "token-label": "Personal Access Token", + "token-required": "GitHub token is required", + "url-description": "The GitHub repository URL", + "url-label": "Repository URL", + "url-pattern": "Must be a valid GitHub repository URL", + "url-required": "Repository URL is required" + }, + "gitlab": { + "branch-description": "The branch to use for provisioning", + "branch-label": "Branch", + "branch-required": "Branch is required", + "path-description": "Optional subdirectory path within the repository", + "path-label": "Path", + "pr-workflow-description": "Allows users to choose whether to open a merge request when saving changes. If the repository does not allow direct changes to the main branch, a merge request may still be required.", + "pr-workflow-label": "Enable merge request option when saving", + "token-description": "GitLab Project Access Token with repository permissions", + "token-label": "Project Access Token", + "token-required": "GitLab token is required", + "url-description": "The GitLab repository URL", + "url-label": "Repository URL", + "url-pattern": "Must be a valid GitLab repository URL", + "url-required": "Repository URL is required" + }, "history-view": { "not-found": "Not found" }, @@ -10671,9 +11222,17 @@ "starting": "Starting...", "status": { "title-error-running-job": "Error running job", - "title-job-completed-successfully": "Job completed successfully" + "title-warning-running-job": "Job completed with warnings" }, - "summary": "Summary" + "summary": "Summary", + "title": { + "error-fetching-active-job": "Error fetching active job" + } + }, + "local": { + "path-description": "Local file system path to the repository", + "path-label": "Repository Path", + "path-required": "Repository path is required" }, "mode-options": { "folder": { @@ -10699,7 +11258,7 @@ }, "repository-actions": { "settings": "Settings", - "source-code": "Source Code" + "source-code": "Source code" }, "repository-card": { "get-repository-meta": { @@ -10764,6 +11323,24 @@ "title-legacy-storage": "Legacy Storage", "title-queued-for-deletion": "Queued for deletion" }, + "repository-type-cards": { + "choose-provider": "Choose a provider:", + "configure-file": "Configure file provisioning", + "configure-with-provider": "Configure with {{ provider }}", + "provider-not-listed": "If your provider is not listed:" + }, + "repository-types": { + "bitbucket": "Bitbucket", + "bitbucket-description": "Connect to Bitbucket repositories", + "github": "GitHub", + "github-description": "Connect to GitHub repositories", + "gitlab": "GitLab", + "gitlab-description": "Connect to GitLab repositories", + "local": "Local", + "local-description": "Configure file provisioning", + "pure-git": "Pure Git", + "pure-git-description": "Connect to any Git repository" + }, "resource-view": { "base": "Base", "dashboard-preview": "Dashboard Preview", @@ -10793,6 +11370,11 @@ "label-current-step": "Current step", "label-pending-step": "Pending step" }, + "sync-job": { + "error-no-job-id": "Failed to start job", + "error-no-repository-name": "No repository name provided", + "error-starting-job": "Error starting job" + }, "sync-repository": { "body-edit-configuration": "Edit the configuration", "button-edit": "Edit", @@ -10803,9 +11385,6 @@ "tooltip-unhealthy-repository": "Unable to pull an unhealthy repository" }, "synchronize-step": { - "error-no-job-id": "Failed to start job", - "error-no-repository-name": "No repository name provided", - "error-starting-job": "Error starting job", "synchronization-description": "Include commits for each historical value", "synchronization-options": "Synchronization options" }, @@ -10814,6 +11393,7 @@ "go-to": "Go to", "make-sure": "Make sure to include these permissions" }, + "warning-title-default": "Warning", "wizard": { "alert-point-1": "Resources won't be able to be created, edited, or deleted during this process. In the last step, they will disappear.", "alert-point-2": "Once provisioning is complete, resources will reappear and be managed through external storage.", @@ -11881,7 +12461,6 @@ }, "label-apply-to-entire-row": "Apply to entire row", "label-background-display-mode": "Background display mode", - "label-wrap-text": "Wrap text", "wrap-text": "Wrap text" }, "column-alignment-options": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index bfca9573a22..ccab92fde0b 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Editar grupo de evaluación" }, "group-loader": { - "group-load-failed": "Error al cargar las reglas del grupo {{ groupName }} en {{ namespaceName }}" + "group-load-failed": "Error al cargar las reglas del grupo {{ groupName }} en {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "El grupo se está eliminando" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configurar", - "draft-new-rule": "Redactar una nueva regla", "ds-error": { "title": "No se pueden cargar las reglas para esta fuente de datos" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nueva regla de alerta", "new-datasource-recording-rule": "Nueva regla de registro de fuente de datos", "new-grafana-recording-rule": "Nueva regla de registro de Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Mostrar más..." }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Actualizar vista previa", + "empty-state": "", "label-preview": "Vista previa", "refresh": "Actualizar" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Nombre del atributo del token de ID", "login-attribute-path-description": "Expresión JMESPath que se utilizará para la búsqueda de inicios de sesión del usuario a partir del token de ID de usuario.", "login-attribute-path-label": "Ruta del atributo de inicio de sesión", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "El ID de cliente de identidad administrada de la credencial de identidad federada de su aplicación OAuth2.", "managed-identity-client-id-label": "ID de cliente de identidad gestionada de FIC", "name-attribute-path-description": "Expresión JMESPath que se utilizará para la búsqueda de nombres de usuario a partir del token de ID de usuario. \nEste nombre se utilizará como nombre para mostrar del usuario.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Activado", "text-badge-not-enabled": "No habilitada" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "El punto final .well-known/openid-configuration para tu IdP", "title-open-id-connect-discovery-url": "URL de OpenID Connect Discovery" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Cancelar", "cannot-move-folders": "Las carpetas no se pueden mover", "confirmation-text": "Eliminar", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Esta carpeta está vacía" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} regla de alerta", "alertRule_other": "{{count}} reglas de alerta", @@ -3554,6 +3582,7 @@ "empty-message": "No se ha encontrado ninguna carpeta", "error-title": "Error al cargar las carpetas", "non-folder-item": "{{itemKind}} {{itemUID}} no es una carpeta", + "root-title": "", "search-placeholder": "Buscar carpetas", "unknown-error": "Error desconocido" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "Añadir {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Cambiar fondo del panel", "panel-description": "Cambiar descripción del panel", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Panel de control", + "element": "", "local-variable": "Variable local", "multiple-elements": "Múltiples elementos", "multiple-elements-delete-text": "¿Seguro que quieres eliminar estos elementos?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Otra persona ha actualizado este dashboard", "would-still-dashboard": "¿Seguro que quieres guardar este dashboard?" }, - "schema-v2-library-panels-export": "La funcionalidad del dashboard dinámico es experimental y no tiene una paridad de características completa con el comportamiento actual de los dashboards. Se basa en un nuevo formato de esquema, que no admite paneles de librería. Esto significa que al exportar el dashboard para usarlo en otra instancia, no se incluirán paneles de librería. Tenemos la intención de admitirlos a medida que avanzamos en el <2>ciclo de vida de la función.", - "schema-v2-library-panels-export-title": "El esquema de dashboard V2 aún no admite la exportación de paneles de librería para su uso en otra instancia", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "El dashboard ha cambiado de forma irreversible" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Hora de comienzo:" } }, + "share-span": "", "warnings": "Advertencias" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Mes anterior", "select-placeholder": "Seleccionar fecha y hora" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Cerrar" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Sin valores", - "pagination-summary": "{{itemsRangeStart}}-{{displayedEnd}} de {{numRows}} filas" + "pagination-summary": "{{itemsRangeStart}}-{{displayedEnd}} de {{numRows}} filas", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Etiquetas" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Copiar línea de registro", "hide-details": "Mostrar detalles del log", "icon-label": "Menú de registro", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Anclar registro", "show-context": "Mostrar contexto", "show-details": "Ocultar detalles del log", @@ -9682,7 +10196,9 @@ "aria-label": "Ayuda" }, "invite-user": { - "invite-new-member-button": "Invitar a un nuevo miembro" + "invite-button": "", + "invite-new-member-button": "Invitar a un nuevo miembro", + "invite-tooltip": "" }, "item": { "add-bookmark": "Añadir a marcadores", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Esta función se encuentra actualmente en desarrollo activo. Para obtener la mejor experiencia y las últimas mejoras, recomendamos utilizar la <2>compilación nocturna de Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Añade un nombre claro para esta conexión de repositorio", "empty": "Vacío", @@ -10460,8 +10998,12 @@ "label-display-name": "Nombre que mostrar", "placeholder-my-repository-connection": "Mi conexión de repositorio", "text-loading-resource-information": "Cargando información de recursos...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} archivos", - "files-count_other": "{{count}} archivos" + "files-count_other": "{{count}} archivos", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Verificar" @@ -10472,36 +11014,20 @@ "button-save": "Guardar", "button-saving": "Guardando…", "description-enabled": "Una vez que la extracción automática está habilitada, el objetivo no se puede cambiar.", - "description-path": "Ruta a un subdirectorio en el repositorio Git", "description-read-only": "Los recursos no se pueden modificar a través de Grafana.", - "description-repository-url": "Introduce la URL del repositorio GitHub", "description-title": "Un nombre legible para la configuración", "error-required": "Este campo es obligatorio.", "error-save-repository": "Error al guardar los ajustes del repositorio", - "error-valid-github-url": "Introduce una URL de repositorio GitHub válida", "label-automatic-pulling": "Extracción automática", - "label-branch": "Rama", "label-enabled": "Activado", - "label-github-token": "Token de GitHub", "label-interval-seconds": "Intervalo (segundos)", - "label-local-path": "Ruta local", - "label-path": "Ruta", - "label-pr-workflow": "Habilitar la opción de solicitud de extracción al guardar", "label-repository-type": "Tipo de repositorio", - "label-repository-url": "URL del repositorio", "label-target": "Destino", "label-title": "Título", "option-entire-instance": "Instancia completa", - "option-github": "GitHub", - "option-local": "Local", "option-managed-folder": "Carpeta administrada", - "placeholder-branch": "principal", - "placeholder-github-token": "ghp_tuTokenAquí1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/nombre-usuario/nombre-repo", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/ruta/a/repo", - "placeholder-my-config": "Mi configuración", - "placeholder-select-repository-type": "Seleccionar tipo de repositorio" + "placeholder-my-config": "Mi configuración" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configura webhooks para obtener actualizaciones instantáneas en Grafana en cuanto se realicen los cambios. Revisa y aprueba los cambios mediante solicitudes de extracción antes de que se publiquen.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configurar", - "configure-file": "Configurar el aprovisionamiento de archivos", - "configure-git-sync": "Configurar Git Sync", "repository-limit-info-alert": "Se ha alcanzado el límite del repositorio ({{count}})" }, - "connect-step": { - "description-branch": "Rama que se utilizará para el repositorio GitHub", - "description-github-path": "Esta es la ruta a un subdirectorio en tu repositorio de GitHub donde se almacenarán y aprovisionarán los paneles", - "description-paste-your-git-hub-personal-access-token": "Pega tu token de acceso personal de GitHub", - "description-repository-url": "Pega la URL de tu repositorio de GitHub", - "error-field-required": "Este campo es obligatorio.", - "error-invalid-github-url": "Introduce una URL de repositorio GitHub válida", - "label-access-token": "Token de acceso a GitHub", - "label-branch": "Nombre de la rama", - "label-local-path": "Ruta local", - "label-path": "Ruta al subdirectorio en el repositorio", - "label-repository-url": "URL del repositorio GitHub", - "placeholder-branch": "principal", - "placeholder-github-token": "github_pat_tuTokenAquí1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/nombre_de_usuario/repo", - "placeholder-local-path": "/ruta/a/repo" - }, "delete-repository-button": { "button-delete": "Eliminar", "confirm-delete-repository": "¿Seguro que quieres eliminar la configuración del repositorio?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Actualizaciones instantáneas y solicitudes de extracción con webhooks.", "title-visual-previews-in-pull-requests": "Vistas previas visuales en solicitudes de extracción con renderizado de imágenes" }, + "error-title-default": "", "expanded-row": { "job-specification": "Especificación del trabajo", - "summary": "Resumen", - "title-error": "Error" + "summary": "Resumen" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Configurar los conmutadores de características necesarios" }, - "manage-dashboards-provision-updates-automatically": "Gestiona los dashboards como código en GitHub y aprovisiona las actualizaciones de forma automática", - "manage-your-dashboards-with-remote-provisioning": "Comenzar con GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Almacena los dashboards en un almacenamiento controlado por versiones para mejorar la organización y el seguimiento del historial" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Añade una vista previa de la imagen de los cambios del dashboard en las solicitudes de extracción. Las imágenes de tus paneles de Grafana se compartirán en tu repositorio Git y serán visibles para cualquier persona con acceso al repositorio.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Requiere renderizado de imágenes. <2>Configurar el renderizado de imágenes", - "description-often-shall-instance-updates-git-hub": "¿Con qué frecuencia la instancia extraerá actualizaciones de GitHub?", - "description-pr-enable-description": "Permite a los usuarios elegir si desean abrir una solicitud de extracción al guardar los cambios. Si el repositorio no permite cambios directos en la rama principal, es posible que aún se requiera una solicitud de extracción.", + "description-preview-requirements": "", "description-read-only": "Los recursos no se pueden modificar a través de Grafana.", - "description-webhooks-enable": "Permite a los usuarios elegir si desean abrir una solicitud de extracción al guardar los cambios. Si el repositorio no permite cambios directos en la rama principal, es posible que aún se requiera una solicitud de extracción.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Habilitar las vistas previas del dashboard en las solicitudes de extracción", - "label-pr-workflow": "Habilitar la opción de solicitud de extracción al guardar", + "label-generate-dashboard-previews": "", "label-read-only": "Solo lectura", - "label-update-instance-interval-seconds": "Actualizar el intervalo de la instancia (segundos)", - "placeholder": "60", - "text-setup-later": "Siempre puedes configurar esto más tarde", - "title-enhance-github": "Mejora tu experiencia en GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "No hay resultados que coincidan con tu consulta", @@ -10640,6 +11145,57 @@ "header": "Aprovisionamiento", "subtitle-provisioning-feature": "Ver y gestionar tus conexiones de aprovisionamiento" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "No encontrado" }, @@ -10666,9 +11222,17 @@ "starting": "Empezando...", "status": { "title-error-running-job": "Error al ejecutar el trabajo", - "title-job-completed-successfully": "Trabajo completado con éxito" + "title-warning-running-job": "" }, - "summary": "Resumen" + "summary": "Resumen", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Configuración", - "source-code": "Código fuente" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Almacenamiento heredado", "title-queued-for-deletion": "En cola para su eliminación" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Base", "dashboard-preview": "Vista previa del dashboard", @@ -10788,6 +11370,11 @@ "label-current-step": "Paso actual", "label-pending-step": "Paso pendiente" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Editar la configuración", "button-edit": "Editar", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "No se puede extraer un repositorio que no está en buen estado" }, "synchronize-step": { - "error-no-job-id": "Error al iniciar el trabajo", - "error-no-repository-name": "No se proporcionó ningún nombre de repositorio", - "error-starting-job": "Error al iniciar el trabajo", "synchronization-description": "Incluir confirmaciones para cada valor histórico", "synchronization-options": "Opciones de sincronización" }, @@ -10809,6 +11393,7 @@ "go-to": "Ir a", "make-sure": "Asegúrate de incluir estos permisos" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Los recursos no se podrán crear, editar ni eliminar durante este proceso. En el último paso, desaparecerán.", "alert-point-2": "Una vez que se complete el aprovisionamiento, los recursos volverán a aparecer y se gestionarán a través de un almacenamiento externo.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Aplicar a toda la fila", "label-background-display-mode": "Modo de visualización del fondo", - "label-wrap-text": "Ajustar texto", "wrap-text": "Ajustar texto" }, "column-alignment-options": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index b0bfe4591cb..e5741e79d7d 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Modifier le groupe d’évaluation" }, "group-loader": { - "group-load-failed": "Échec du chargement des règles du groupe {{ groupName }} dans {{ namespaceName }}" + "group-load-failed": "Échec du chargement des règles du groupe {{ groupName }} dans {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Le groupe est en cours de suppression" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configurer", - "draft-new-rule": "Rédiger une nouvelle règle", "ds-error": { "title": "Impossible de charger les règles pour cette source de données" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nouvelle règle d'alerte", "new-datasource-recording-rule": "Nouvelle règle d’enregistrement de source de données", "new-grafana-recording-rule": "Nouvelle règle d’enregistrement Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Afficher plus..." }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Actualiser l’aperçu", + "empty-state": "", "label-preview": "Aperçu", "refresh": "Actualiser" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Nom d’attribut du jeton d’identification", "login-attribute-path-description": "Expression JMESPath à utiliser pour la recherche de connexion de l’utilisateur à partir du jeton d’identification de l’utilisateur.", "login-attribute-path-label": "Chemin d’attribut de connexion", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "L’ID du client de l’identité gérée de l’identifiant de l’identité fédérée de votre application OAuth2.", "managed-identity-client-id-label": "ID du client de l’identité gérée FIC", "name-attribute-path-description": "Expression JMESPath à utiliser pour la recherche de nom d’utilisateur à partir du jeton d’identification de l’utilisateur. \nCe nom sera utilisé comme nom d’affichage de l’utilisateur.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Activé", "text-badge-not-enabled": "Non activé" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Le point de terminaison .well-known/openid-configuration pour votre IdP", "title-open-id-connect-discovery-url": "URL de découverte de connexion OpenID" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Annuler", "cannot-move-folders": "Les dossiers ne peuvent pas être déplacés", "confirmation-text": "Supprimer", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Ce dossier est vide" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} règle d'alerte", "alertRule_other": "{{count}} règles d'alerte", @@ -3554,6 +3582,7 @@ "empty-message": "Aucun dossier trouvé", "error-title": "Impossible de charger les dossiers", "non-folder-item": "Non-dossier {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Rechercher dans les dossiers", "unknown-error": "Erreur inconnue" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "Ajouter {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "Déplacer {{typeName}}", "panel-background": "Modifier l’arrière-plan du panneau", "panel-description": "Modifier la description du panneau", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Tableau de bord", + "element": "", "local-variable": "Variable locale", "multiple-elements": "Éléments multiples", "multiple-elements-delete-text": "Voulez-vous vraiment supprimer ces éléments ?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Quelqu’un d’autre a mis à jour ce tableau de bord", "would-still-dashboard": "Voulez-vous toujours enregistrer ce tableau de bord ?" }, - "schema-v2-library-panels-export": "La fonctionnalité de tableau de bord dynamique est expérimentale et n’a pas une parité complète de fonctionnalités avec le comportement actuel des tableaux de bord. Elle est basée sur un nouveau format de schéma, qui ne prend pas en charge les panneaux de bibliothèque. Cela signifie que lors de l’exportation du tableau de bord pour l’utiliser dans une autre instance, nous n’inclurons pas les panneaux de la bibliothèque. Nous avons l’intention de les prendre en charge au fur et à mesure de notre progression dans le <2>cycle de vie de la fonctionnalité.", - "schema-v2-library-panels-export-title": "Le schéma de tableau de bord V2 ne prend pas encore en charge l’exportation de panneaux de bibliothèque à utiliser dans une autre instance", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Le tableau de bord a été modifié de manière irréversible" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Heure de début :" } }, + "share-span": "", "warnings": "Avertissements" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Mois précédent", "select-placeholder": "Sélectionner la date/l'heure" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Fermer" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "Sélectionner un champ" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "Champs dont le nom correspond à une expression régulière", "name-fields-by-query": "Champs retournés par la requête", "name-fields-with-name": "Champs avec ce nom", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Aucune valeur", - "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} sur {{numRows}} lignes" + "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} sur {{numRows}} lignes", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Balises" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "Aucun champ à afficher.", + "open-assistant": "", "pin-line": "Épingler ce log", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "Aucun résultat à afficher." }, @@ -8849,6 +9360,9 @@ "copy-log": "Copier la ligne de journal", "hide-details": "Afficher les détails du journal", "icon-label": "Menu du journal", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Épingler le journal", "show-context": "Afficher le contexte", "show-details": "Masquer les détails du journal", @@ -9682,7 +10196,9 @@ "aria-label": "Aide" }, "invite-user": { - "invite-new-member-button": "Inviter un nouveau membre" + "invite-button": "Inviter", + "invite-new-member-button": "Inviter un nouveau membre", + "invite-tooltip": "" }, "item": { "add-bookmark": "Ajouter aux signets", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Cette fonctionnalité est actuellement en cours de développement. Pour une expérience optimale et les dernières améliorations, nous vous recommandons d’utiliser la <2>compilation nocturne de Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Ajoutez un nom clair pour cette connexion de référentiel", "empty": "Vide", @@ -10460,8 +10998,12 @@ "label-display-name": "Pseudo", "placeholder-my-repository-connection": "Ma connexion au référentiel", "text-loading-resource-information": "Chargement des informations sur les ressources...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} fichiers", - "files-count_other": "{{count}} fichiers" + "files-count_other": "{{count}} fichiers", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Vérifier" @@ -10472,36 +11014,20 @@ "button-save": "Enregistrer", "button-saving": "Enregistrement en cours...", "description-enabled": "Une fois que la fusion automatique est activée, la cible ne peut pas être modifiée.", - "description-path": "Chemin d’accès à un sous-répertoire dans le référentiel Git", "description-read-only": "Les ressources ne peuvent pas être modifiées via Grafana.", - "description-repository-url": "Saisir l’URL du référentiel GitHub", "description-title": "Un nom lisible par l’utilisateur pour la configuration", "error-required": "Ce champ est obligatoire.", "error-save-repository": "Échec de l’enregistrement des paramètres du référentiel", - "error-valid-github-url": "Veuillez saisir une URL de référentiel GitHub valide", "label-automatic-pulling": "Fusion automatique", - "label-branch": "Branche", "label-enabled": "Activé", - "label-github-token": "Jeton GitHub", "label-interval-seconds": "Intervalle (secondes)", - "label-local-path": "Chemin local", - "label-path": "Chemin", - "label-pr-workflow": "Activer l’option de demande de fusion lors de l’enregistrement", "label-repository-type": "Type de référentiel", - "label-repository-url": "URL du référentiel", "label-target": "Cible", "label-title": "Titre", "option-entire-instance": "Instance entière", - "option-github": "GitHub", - "option-local": "Local", "option-managed-folder": "Dossier géré", - "placeholder-branch": "principal", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Ma configuration", - "placeholder-select-repository-type": "Sélectionner un type de référentiel" + "placeholder-my-config": "Ma configuration" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configurez les webhooks pour obtenir des mises à jour instantanées dans Grafana dès que les modifications sont validées. Examinez et approuvez les modifications à l’aide de demandes de fusion avant leur mise en ligne.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configurer", - "configure-file": "Configurer la mise en service de fichiers", - "configure-git-sync": "Configurer la synchronisation Git", "repository-limit-info-alert": "Limite du référentiel atteinte ({{count}})" }, - "connect-step": { - "description-branch": "Branche à utiliser pour le référentiel GitHub", - "description-github-path": "Il s’agit du chemin d’accès à un sous-répertoire dans votre référentiel GitHub où les tableaux de bord seront stockés et mis en service à partir de", - "description-paste-your-git-hub-personal-access-token": "Collez votre jeton d’accès personnel GitHub", - "description-repository-url": "Coller l’URL du référentiel GitHub", - "error-field-required": "Ce champ est obligatoire.", - "error-invalid-github-url": "Veuillez saisir une URL de référentiel GitHub valide", - "label-access-token": "Jeton d’accès à GitHub", - "label-branch": "Nom de la branche", - "label-local-path": "Chemin local", - "label-path": "Chemin d’accès au sous-répertoire dans le référentiel", - "label-repository-url": "URL de référentiel GitHub", - "placeholder-branch": "principal", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Supprimer", "confirm-delete-repository": "Voulez-vous vraiment supprimer la configuration du référentiel ?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Mises à jour instantanées et demandes de fusion avec les webhooks.", "title-visual-previews-in-pull-requests": "Aperçus visuels dans les demandes de fusion avec rendu d’image" }, + "error-title-default": "", "expanded-row": { "job-specification": "Description de la mission", - "summary": "Synthèse", - "title-error": "Erreur" + "summary": "Synthèse" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Configurer les basculements de fonctionnalité requis" }, - "manage-dashboards-provision-updates-automatically": "Gérez les tableaux de bord sous forme de code dans GitHub et mettez automatiquement les mises à jour en service", - "manage-your-dashboards-with-remote-provisioning": "Premiers pas avec GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Stockez les tableaux de bord dans un espace de stockage à gestion des versions pour une meilleure organisation et un meilleur suivi de l’historique" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Ajoute un aperçu des images des modifications apportées au tableau de bord dans les demandes de fusion. Les images de vos tableaux de bord Grafana seront partagées dans votre référentiel Git et visibles par toute personne ayant accès au référentiel.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Nécessite un rendu d’image. <2>Configurer le rendu d’image", - "description-often-shall-instance-updates-git-hub": "À quelle fréquence l’instance doit-elle extraire les mises à jour de GitHub ?", - "description-pr-enable-description": "Permet aux utilisateurs de choisir d’ouvrir ou non une demande de fusion lors de l’enregistrement des modifications. Si le référentiel n’autorise pas les modifications directes de la branche principale, il est possible qu’une demande de fusion soit nécessaire.", + "description-preview-requirements": "", "description-read-only": "Les ressources ne peuvent pas être modifiées via Grafana.", - "description-webhooks-enable": "Permet aux utilisateurs de choisir d’ouvrir ou non une demande de fusion lors de l’enregistrement des modifications. Si le référentiel n’autorise pas les modifications directes de la branche principale, il est possible qu’une demande de fusion soit nécessaire.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Activer les aperçus du tableau de bord dans les demandes de fusion", - "label-pr-workflow": "Activer l’option de demande de fusion lors de l’enregistrement", + "label-generate-dashboard-previews": "", "label-read-only": "Lecture seule", - "label-update-instance-interval-seconds": "Mettre à jour l’intervalle d’instance (secondes)", - "placeholder": "60", - "text-setup-later": "Vous pouvez toujours configurer cela plus tard", - "title-enhance-github": "Améliorez votre expérience GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Aucun résultat ne correspond à votre requête", @@ -10640,6 +11145,57 @@ "header": "Mise en service", "subtitle-provisioning-feature": "Afficher et gérer vos connexions de mise en service" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Introuvable" }, @@ -10666,9 +11222,17 @@ "starting": "Démarrage...", "status": { "title-error-running-job": "Erreur lors de l’exécution de la mission", - "title-job-completed-successfully": "Mission accomplie" + "title-warning-running-job": "" }, - "summary": "Synthèse" + "summary": "Synthèse", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Paramètres", - "source-code": "Code source" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Stockage hérité", "title-queued-for-deletion": "Mis en attente pour suppression" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Base", "dashboard-preview": "Aperçu du tableau de bord", @@ -10788,6 +11370,11 @@ "label-current-step": "Étape actuelle", "label-pending-step": "Étape en attente" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Modifier la configuration", "button-edit": "Modifier", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Impossible de fusionner un référentiel en mauvais état" }, "synchronize-step": { - "error-no-job-id": "Échec du lancement de la mission", - "error-no-repository-name": "Aucun nom de référentiel fourni", - "error-starting-job": "Erreur lors du lancement de la mission", "synchronization-description": "Inclure les commits pour chaque valeur historique", "synchronization-options": "Options de synchronisation" }, @@ -10809,6 +11393,7 @@ "go-to": "Accéder à", "make-sure": "Assurez-vous d’inclure ces autorisations" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Les ressources ne pourront pas être créées, modifiées ou supprimées pendant ce processus. Lors de la dernière étape, elles disparaîtront.", "alert-point-2": "Une fois la mise en service terminée, les ressources réapparaîtront et seront gérées via un stockage externe.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Appliquer à toute la ligne", "label-background-display-mode": "Mode d’affichage de l’arrière-plan", - "label-wrap-text": "Renvoyer à la ligne automatiquement", "wrap-text": "Renvoyer à la ligne automatiquement" }, "column-alignment-options": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index f5b81f0455f..7731b5bd4eb 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -119,10 +119,10 @@ "users-heading": "Szervezeti felhasználók" }, "enterprise-auth-features-card": { - "dismiss": "", - "heading": "", - "learn-more-link": "", - "text": "" + "dismiss": "Elvetés", + "heading": "Vállalati hitelesítés", + "learn-more-link": "További információk", + "text": "Kezelje automatikusan a felhasználókat, a csapatokat és az engedélyeket a Grafana Cloud és az Enterprise szolgáltatásban elérhető <1>SAML, <3>SCIM, <6>LDAP és <8>RBAC segítségével." }, "feature-listing": { "title-auditing": "Auditálás", @@ -374,8 +374,8 @@ "am-active-timing-select-label-active-timings": "Aktív időzítések" }, "ad": { - "close": "", - "learn-more": "" + "close": "Bezárás", + "learn-more": "További információk" }, "add-button": { "add-more": "Továbbiak hozzáadása" @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -404,12 +405,12 @@ "missing-series-resolve": "Megoldandó hiányzó sorozatértékelések", "no-annotations": "Jegyzetek nélkül", "notification-configuration": { - "active-timings": "", + "active-timings": "Aktív időintervallumok", "contact-point": "Kapcsolattartási pont", "group-by": "Csoportosítás alapja", - "group-title": "", + "group-title": "Értesítési konfiguráció", "mute-timings": "Némítási időzítések", - "timing-options": "" + "timing-options": "Időzítések" }, "pending-period": "Függőben lévő időszak", "rule": "Szabály", @@ -963,7 +964,7 @@ }, "contactPoints": { "validation": { - "notFound": "" + "notFound": "A(z) „{{contactPoint}}” kapcsolattartási pont nem található" } }, "continue-matching-indicator": { @@ -972,7 +973,7 @@ "copy-to-clipboard": "„{{label}}” másolása a vágólapra", "create-metadata": { "label": { - "contact-point": "", + "contact-point": "Az értesítések a következő címre érkeznek:", "dashboard": "Irányítópult", "dashboard-and-panel": "Irányítópult és panel", "evaluation-interval": "Értékelési intervallum", @@ -1120,7 +1121,7 @@ "content-show-all-errors": "Összes hiba megjelenítése" }, "errors": { - "failedWith": "" + "failedWith": "{{-config}} sikertelen {{status}} állapottal: {{-message}}" }, "evaluate-every-validation-options": { "message": { @@ -1551,7 +1552,8 @@ "title": "Értékelési csoport szerkesztése" }, "group-loader": { - "group-load-failed": "A szabályok betöltése a(z) {{ groupName }} csoportból nem sikerült a(z) {{ namespaceName }} névtérben" + "group-load-failed": "A szabályok betöltése a(z) {{ groupName }} csoportból nem sikerült a(z) {{ namespaceName }} névtérben", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "A csoport törlése folyamatban van" @@ -1568,20 +1570,20 @@ } }, "home": { - "irm-card-description": "", - "irm-card-item-1": "", - "irm-card-item-2": "", - "irm-card-item-3": "", - "irm-card-item-4": "", - "irm-card-title": "", + "irm-card-description": "Egyesítse az ügyeleti, riasztási és incidenskezelési funkciókat a Grafana Cloud IRM segítségével.", + "irm-card-item-1": "Ügyeleti időbeosztások kezelése a naptárral vagy a Terraformmal.", + "irm-card-item-2": "Válaszoljon az incidensekre a weben, az alkalmazáson, a Slacken vagy más csatornán keresztül.", + "irm-card-item-3": "Határozza meg a kiváltó okokat az MI-alapú Grafana SIFT segítségével.", + "irm-card-item-4": "Elemezze a múltbeli incidenseket a válaszok és a rugalmasság javítása érdekében.", + "irm-card-title": "Incidensválaszok és -kezelés", "label-get-started": "Kezdés", "label-insights": "Adatok", - "synthetic-monitoring-card-description": "", - "synthetic-monitoring-card-item-1": "", - "synthetic-monitoring-card-item-2": "", - "synthetic-monitoring-card-item-3": "", - "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", + "synthetic-monitoring-card-description": "Figyelje a kritikus felhasználói folyamatokat, webhelyeket és API-kat kívülről, globális helyekről.", + "synthetic-monitoring-card-item-1": "Szimulálja a végpontok közötti felhasználói utakat böngészőellenőrzésekkel.", + "synthetic-monitoring-card-item-2": "Futtasson pingelést, DNS-, HTTP/S- és TCP-ellenőrzéseket minden hálózati rétegben.", + "synthetic-monitoring-card-item-3": "Használhat több mint 20 globális szondát vagy privát szondát a tűzfal mögött.", + "synthetic-monitoring-card-item-4": "Kövesse nyomon az SLO-kat beépített Prometheus-stílusú riasztásokkal – közvetlenül a felhasználói felületről.", + "synthetic-monitoring-card-title": "Szintetikus megfigyelés", "title-alerting": "Riasztás" }, "import-to-gma": { @@ -1596,7 +1598,7 @@ "no-rules-body-yaml": "Nincsenek importálandó szabályok. Válasszon másik yaml-fájlt.", "no-rules-title": "Nincsenek importálandó szabályok", "not-using-rules-managed-by-integrations-or-plugins": { - "text": "", + "text": "Az integrációk vagy beépülő modulok által kezelt szabályokat nem szabad importálni a Grafana által kezelt szabályokba.", "title": "Információ" }, "plugin-rules-warning": { @@ -1976,7 +1978,7 @@ }, "notification-policies-filter": { "label-search-by-contact-point": "Keresés kapcsolattartási pont alapján", - "placeholder-search-by-contact-point": "", + "placeholder-search-by-contact-point": "Válasszon egy kapcsolattartási pontot", "search-query-input-placeholder-search": "Keresés" }, "notification-policies-list": { @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Konfigurálás", - "draft-new-rule": "Új szabály felvázolása", "ds-error": { "title": "Nem lehet betölteni a szabályokat ehhez az adatforráshoz" }, @@ -2487,11 +2488,12 @@ "new-badge": "Új!", "text": "Importálás a Grafana által felügyelt szabályokba" }, - "loading-more-groups": "", + "loading-more-groups": "További csoportok betöltése…", "more": "Továbbiak", "new-alert-rule": "Új riasztási szabály", "new-datasource-recording-rule": "Új adatforrás-felvételkészítési szabály", "new-grafana-recording-rule": "Új Grafana-felvételkészítési szabály", + "new-rule-for-export": "", "pagination": { "next-page": "Továbbiak megjelenítése…" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Előnézet frissítése", + "empty-state": "", "label-preview": "Előnézet", "refresh": "Frissítés" }, @@ -3038,51 +3041,51 @@ } }, "alertlist": { - "category-alert-state-filter": "", - "category-filter": "", - "category-options": "", - "descriptino-alerts-linked-to-dashboard": "", - "description-alert-instance-label": "", - "description-alert-name": "", - "description-datasource": "", - "description-folder": "", - "description-group-by": "", - "description-group-mode": "", - "description-max-items": "", - "description-show-inactive-alerts": "", - "description-sort-order": "", - "description-view-mode": "", + "category-alert-state-filter": "Riasztásiállapot-szűrő", + "category-filter": "Szűrő", + "category-options": "Beállítások", + "descriptino-alerts-linked-to-dashboard": "Csak az ehhez az irányítópulthoz kapcsolódó riasztások megjelenítése", + "description-alert-instance-label": "Szűrje a riasztási példányokat címkelekérdezéssel, pl.: {severity=\"critical\", instance=~\"cluster-us-.+\"}", + "description-alert-name": "Az ezt a szöveget tartalmazó riasztások szűrése", + "description-datasource": "Szűrés a riasztás forrásából", + "description-folder": "A kijelölt mappában lévő riasztások szűrése (csak a Grafana által felügyelt riasztási szabályok esetén)", + "description-group-by": "Riasztások szűrése címkelekérdezéssel", + "description-group-mode": "A riasztáspéldányok csoportosításának módja", + "description-max-items": "Megjelenítendő riasztások maximális száma", + "description-show-inactive-alerts": "Tartalmazza 0 (nulla) példánnyal rendelkező riasztási szabályokat. Mivel ezeknek a szabályoknak nincsenek példányai, rejtve maradnak, ha a riasztáspéldány-címkeszűrő konfigurálva van.", + "description-sort-order": "Riasztások és riasztáspéldányok rendezési sorrendje", + "description-view-mode": "Váltás a listanézet és a statisztikai nézet között", "group-by": { "aria-label-group-by-label-keys": "címkekulcsok szerinti csoportosítás", "placeholder-group-by": "Csoportosítási szempont" }, "group-mode-options": { - "label-custom-grouping": "", - "label-default-grouping": "" + "label-custom-grouping": "Egyéni csoportosítás", + "label-default-grouping": "Alapértelmezett csoportosítás" }, - "name-alert-instance-label": "", - "name-alert-name": "", - "name-alerts-linked-to-dashboard": "", - "name-datasource": "", - "name-error": "", - "name-firing": "", - "name-folder": "", - "name-group-by": "", - "name-group-mode": "", - "name-max-items": "", - "name-no-data": "", - "name-normal": "", - "name-pending": "", - "name-recovering": "", - "name-show-inactive-alerts": "", - "name-sort-order": "", - "name-view-mode": "", + "name-alert-instance-label": "Riasztáspéldány-címke", + "name-alert-name": "Riasztás neve", + "name-alerts-linked-to-dashboard": "Az irányítópulthoz kapcsolódó riasztások megjelenítése", + "name-datasource": "Adatforrás", + "name-error": "Hiba", + "name-firing": "Riasztás/aktív", + "name-folder": "Mappa", + "name-group-by": "Csoportosítási szempont", + "name-group-mode": "Csoportmód", + "name-max-items": "Elemek max. száma", + "name-no-data": "Nincs adat", + "name-normal": "Normál", + "name-pending": "Függőben", + "name-recovering": "Helyreállítás", + "name-show-inactive-alerts": "0 példánnyal rendelkező riasztások megjelenítése", + "name-sort-order": "Rendezési sorrend", + "name-view-mode": "Nézetmód", "sort-order-options": { - "label-alphabetical-asc": "", - "label-alphabetical-desc": "", - "label-importance": "", - "label-time-asc": "", - "label-time-desc": "" + "label-alphabetical-asc": "Betűrendben (növekvő)", + "label-alphabetical-desc": "Betűrendben (csökkenő)", + "label-importance": "Fontosság", + "label-time-asc": "Idő (növekvő)", + "label-time-desc": "Idő (csökkenő)" }, "ungrouped-mode-view": { "active-for": "ennyi ideig: <1>{{duration}}", @@ -3098,8 +3101,8 @@ "title-permission-required": "Engedély szükséges" }, "view-mode-options": { - "label-list": "", - "label-stat": "" + "label-list": "Lista", + "label-stat": "Statisztika" } }, "annolist": { @@ -3112,31 +3115,31 @@ "annotation-list-item": { "tooltip-created-by": "Létrehozta:<1> {{email}}" }, - "category-annotation-query": "", - "category-display": "", - "category-link-behaviour": "", - "description-tags": "", + "category-annotation-query": "Jegyzetlekérdezés", + "category-display": "Megjelenítés", + "category-link-behaviour": "Hivatkozás viselkedése", + "description-tags": "Jegyzetcímkék egyeztetése", "link-target-options": { - "label-dashboard": "", - "label-panel": "" + "label-dashboard": "Irányítópult", + "label-panel": "Panel" }, - "name-limit": "", - "name-link-target": "", - "name-query-filter": "", - "name-show-tags": "", - "name-show-time": "", - "name-show-user": "", - "name-tags": "", - "name-time-after": "", - "name-time-before": "", - "name-time-range": "", + "name-limit": "Korlát", + "name-link-target": "Hivatkozási cél", + "name-query-filter": "Lekérdezési szűrő", + "name-show-tags": "Címkék megjelenítése", + "name-show-time": "Idő megjelenítése", + "name-show-user": "Felhasználó megjelenítése", + "name-tags": "Címkék", + "name-time-after": "Idő utána", + "name-time-before": "Idő előtte", + "name-time-range": "Időtartomány", "query-filter-options": { - "label-all-dashboards": "", - "label-this-dashboard": "" + "label-all-dashboards": "Összes irányítópult", + "label-this-dashboard": "Ez az irányítópult" }, "time-range-options": { - "label-none": "", - "label-this-dashboard": "" + "label-none": "Nincs", + "label-this-dashboard": "Ez az irányítópult" } }, "annotations": { @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Azonosítótoken-attribútum neve", "login-attribute-path-description": "A felhasználói bejelentkezés felhasználói azonosítótoken alapján végzett kereséséhez használandó JMESPath-kifejezés.", "login-attribute-path-label": "Bejelentkezési attribútum útvonala", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "Az OAuth2-alkalmazás összevont identitáshitelesítő adatainak felügyelt identitáskliens-azonosítója.", "managed-identity-client-id-label": "FIC által felügyelt identitáskliens-azonosító", "name-attribute-path-description": "A felhasználónév felhasználói azonosítótoken alapján végzett kereséséhez használandó JMESPath-kifejezés. \nEz a név lesz a felhasználó megjelenített neve.", @@ -3326,8 +3334,8 @@ } }, "provider-card": { - "saml-learn-more": "", - "scim-learn-more": "", + "saml-learn-more": "Egyszeri bejelentkezés (SSO) SAML használatával.", + "scim-learn-more": " Felhasználók és csapatok szinkronizálása SCIM-mel.", "text-badge-enabled": "Engedélyezve", "text-badge-not-enabled": "Nem engedélyezett" }, @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Engedélyezve", "text-badge-not-enabled": "Nem engedélyezett" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Az identitásszolgáltató .well-known/openid-configuration végpontja", "title-open-id-connect-discovery-url": "Az OpenID Connect Discovery URL-címe" @@ -3364,99 +3375,99 @@ }, "bar-chart": { "warn": { - "missing-numeric": "", - "missing-series": "" + "missing-numeric": "Nem található numerikus mező", + "missing-series": "A sávdiagramokhoz karakterlánc vagy időmező szükséges" } }, "barchart": { "config": { - "category-thresholds": "", - "description-color-by-field": "", - "description-x-label-max-length": "", - "name-bar-radius": "", - "name-bar-width": "", - "name-color-by-field": "", - "name-fill-opacity": "", - "name-full-highlight": "", - "name-gradient-mode": "", - "name-group-width": "", - "name-line-width": "", - "name-orientation": "", - "name-rotate-x-labels": "", - "name-show-thresholds": "", - "name-show-values": "", - "name-stacking": "", - "name-transform": "", - "name-x-axis": "", - "name-x-label-max-length": "", - "name-x-label-min-spacing": "", + "category-thresholds": "Küszöbértékek", + "description-color-by-field": "Használja a testvérmező színértékét az egyes sávértékek színezéséhez.", + "description-x-label-max-length": "Az X-tengely címkéi a megadott hosszúságra lesznek rövidítve", + "name-bar-radius": "Sáv sugara", + "name-bar-width": "Sáv szélessége", + "name-color-by-field": "Szín mező szerint", + "name-fill-opacity": "Kitöltési átlátszóság", + "name-full-highlight": "Teljes terület kiemelése, ha az egérmutatót fölé viszi", + "name-gradient-mode": "Színátmenetes mód", + "name-group-width": "Csoport szélessége", + "name-line-width": "Vonal szélessége", + "name-orientation": "Tájolás", + "name-rotate-x-labels": "X-tengely feliratainak elforgatása", + "name-show-thresholds": "Küszöbértékek megjelenítése", + "name-show-values": "Értékek megjelenítése", + "name-stacking": "Egymásra helyezés", + "name-transform": "Transzformáció", + "name-x-axis": "X tengely", + "name-x-label-max-length": "X tengely feliratainak maximális hossza", + "name-x-label-min-spacing": "X tengely feliratainak minimális távolsága", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-line-vertical": "" + "label-auto": "Automatikus", + "label-horizontal": "Vízszintes", + "label-line-vertical": "Függőleges" }, - "placeholder-x-axis": "", - "placeholder-x-label-max-length": "", + "placeholder-x-axis": "Első karakterlánc vagy időmező", + "placeholder-x-label-max-length": "Nincs", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Mindig", + "label-auto": "Automatikus", + "label-never": "Soha" }, "transform-options": { - "description-constant": "", - "description-negative-y": "", - "label-constant": "", - "label-negative-y": "" + "description-constant": "Az első érték állandó vonalként jelenik meg", + "description-negative-y": "Az eredmények negatív értékekre fordítása az y tengelyen", + "label-constant": "Konstans", + "label-negative-y": "Negatív Y" } }, "tick-spacing-editor": { "content-require-space-from-the-right-side": "Hely szükséges a jobb oldalról", "gaps-options": { - "description-large": "", - "description-medium": "", - "description-none": "", - "description-small": "", - "label-large": "", - "label-medium": "", - "label-none": "", - "label-small": "" + "description-large": "{{spacing}} térközt igényel", + "description-medium": "{{spacing}} térközt igényel", + "description-none": "Összes felirat megjelenítése", + "description-small": "{{spacing}} térközt igényel", + "label-large": "Nagy", + "label-medium": "Közepes", + "label-none": "Nincs", + "label-small": "Kicsi" }, "label-rtl": "RTL" } }, "bargauge": { "bar-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Automatikus", + "label-manual": "Kézi" }, - "category-bar-gauge": "", - "description-max-height": "", - "description-min-height": "", - "description-min-width": "", - "description-show-unfilled-area": "", + "category-bar-gauge": "Sávdiagram", + "description-max-height": "Maximális sormagasság (vízszintes tájolás)", + "description-min-height": "Minimális sormagasság (vízszintes tájolás)", + "description-min-width": "Minimális oszlopszélesség (függőleges tájolás)", + "description-show-unfilled-area": "Ha engedélyezve van, a kitöltetlen területet szürkén jeleníti meg", "display-mode-options": { - "label-basic": "", - "label-gradient": "", - "label-retro": "" + "label-basic": "Alap", + "label-gradient": "Színátmenet", + "label-retro": "Retro LCD" }, - "name-bar-size": "", - "name-display-mode": "", - "name-max-height": "", - "name-min-height": "", - "name-min-width": "", - "name-name-placement": "", + "name-bar-size": "Sáv mérete", + "name-display-mode": "Megjelenítési mód", + "name-max-height": "Max. magasság", + "name-min-height": "Min. magasság", + "name-min-width": "Min. szélesség", + "name-name-placement": "Név elhelyezése", "name-placement-options": { - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-top": "" + "label-auto": "Automatikus", + "label-hidden": "Rejtett", + "label-left": "Balra", + "label-top": "Felül" }, - "name-show-unfilled-area": "", - "name-value-display": "", + "name-show-unfilled-area": "Kitöltetlen terület megjelenítése", + "name-value-display": "Érték megjelenítése", "value-display-options": { - "label-hidden": "", - "label-text-color": "", - "label-value-color": "" + "label-hidden": "Rejtett", + "label-text-color": "Szöveg színe", + "label-value-color": "Érték színe" } }, "bookmarks-page": { @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Mégse", "cannot-move-folders": "A mappák nem helyezhetők át", "confirmation-text": "Törlés", @@ -3481,7 +3491,7 @@ "delete-modal-restore-dashboards-text": "Ez a művelet azonnal törli a kijelölt mappákat, de a kijelölt irányítópultok 30 napon belüli törlésre lesznek megjelölve. A szervezeti rendszergazda a 30 nap lejárta előtt bármikor visszaállíthatja az irányítópultokat. A mappák nem állíthatók vissza.", "delete-modal-text": "Ez a művelet törli a következő tartalmat:", "delete-modal-title": "Törlés", - "delete-provisioned-folder": "", + "delete-provisioned-folder": "Kiépített mappa törlése", "deleting": "Törlés...", "manage-permissions-button": "Engedélyek kezelése", "move-button": "Áthelyezés", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "A mappa üres" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} riasztási szabály", "alertRule_other": "{{count}} riasztási szabály", @@ -3525,12 +3553,12 @@ "tags-column": "Címkék" }, "delete-provisioned-folder-form": { - "alert-folder-deleted-successfully": "", - "api-error": "", - "button-cancel": "", - "button-delete": "", - "button-deleting": "", - "delete-warning": "" + "alert-folder-deleted-successfully": "A mappa törlése sikeres", + "api-error": "A mappa törlése nem sikerült", + "button-cancel": "Mégse", + "button-delete": "Törlés", + "button-deleting": "Törlés...", + "delete-warning": "Ez törli ezt a mappát és minden almappáját. Összességében ez a következőket érinti:" }, "descendant-count": { "title-unable-to-retrieve-descendant-information": "Nem lehet lekérni a leszármazott adatokat" @@ -3554,6 +3582,7 @@ "empty-message": "Nem található mappa", "error-title": "Hiba történt a mappák betöltésekor", "non-folder-item": "Nem mappa {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Mappák keresése", "unknown-error": "Ismeretlen hiba" }, @@ -3589,52 +3618,52 @@ "text": "Nincs találat a lekérdezésre" }, "restore": { - "success": "" + "success": "{{name}} irányítópult visszaállítva" }, "text-this-repository-is-read-only": "Ha közvetlen hozzáférése van a célhoz, másolja ki a JSON-kódot, és illessze be oda.", "trash-state-manager": { "label": { - "alphabetically-az": "", - "alphabetically-za": "" + "alphabetically-az": "Betűrendben (A–Z)", + "alphabetically-za": "Betűrendben (Z–A)" } } }, "candlestick": { "additional-fields-options": { - "label-ignore": "", - "label-include": "" + "label-ignore": "Mellőzés", + "label-include": "Tartalmazza" }, "candle-style-options": { - "label-candles": "", - "label-ohlc-bars": "" + "label-candles": "Gyertyák", + "label-ohlc-bars": "OHLC-sávok" }, - "category-candlestick": "", + "category-candlestick": "Gyertyatartó", "color-strategy-options": { - "label-since-open": "", - "label-since-prior-close": "" + "label-since-open": "Nyitás óta", + "label-since-prior-close": "Az előző zárás óta" }, - "description-additional-fields": "", - "description-close": "", - "description-high": "", - "description-low": "", - "description-open": "", - "description-volume": "", + "description-additional-fields": "Használja a szabványos idősor-beállításokat a fent hozzá nem rendelt mezők konfigurálásához", + "description-close": "Érték a tárgyidőszak végén", + "description-high": "Maximális érték a tárgyidőszakon belül", + "description-low": "Minimális érték a tárgyidőszakon belül", + "description-open": "Érték a tárgyidőszak elején", + "description-volume": "Mintaszám a tárgyidőszakon belül", "mode-options": { - "label-both": "", - "label-candles": "", - "label-volume": "" + "label-both": "Mindkettő", + "label-candles": "Gyertyák", + "label-volume": "Mennyiség" }, - "name-additional-fields": "", - "name-candle-style": "", - "name-close": "", - "name-color-strategy": "", - "name-down-color": "", - "name-high": "", - "name-low": "", - "name-mode": "", - "name-open": "", - "name-up-color": "", - "name-volume": "" + "name-additional-fields": "További mezők", + "name-candle-style": "Gyertya stílusa", + "name-close": "Bezárás", + "name-color-strategy": "Színstratégia", + "name-down-color": "Csökkenés színe", + "name-high": "Magas", + "name-low": "Alacsony", + "name-mode": "Mód", + "name-open": "Megnyitás", + "name-up-color": "Növekedés színe", + "name-volume": "Mennyiség" }, "canvas": { "apieditor": { @@ -3649,18 +3678,18 @@ } }, "button-item": { - "category-button": "", + "category-button": "Gomb", "label": { "center": "Középre", "left": "Balra", "right": "Jobbra" }, - "name-align-text": "", - "name-api": "", - "name-style": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", + "name-align-text": "Szöveg igazítása", + "name-api": "API", + "name-style": "Stílus", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", "placeholder": { "auto": "Automatikus" } @@ -3687,20 +3716,20 @@ } } }, - "category-arrow-direction": "", - "category-background": "", - "category-border": "", - "category-canvas": "", - "category-color": "", - "category-data-links": "", - "category-line-style": "", - "category-metric-value": "", - "category-radius": "", - "category-selected-connection": "", - "category-selected-element": "", - "category-size": "", + "category-arrow-direction": "Nyíl iránya", + "category-background": "Háttér", + "category-border": "Szegély", + "category-canvas": "Vászon", + "category-color": "Szín", + "category-data-links": "Adathivatkozások és műveletek", + "category-line-style": "Vonal stílusa", + "category-metric-value": "Metrikaérték", + "category-radius": "Sugár", + "category-selected-connection": "Kiválasztott kapcsolat", + "category-selected-element": "Kiválasztott elem ({{element}})", + "category-size": "Méret", "cloud-item": { - "category-cloud": "", + "category-cloud": "Felhő", "label": { "bottom": "Lent", "center": "Középre", @@ -3709,48 +3738,48 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } }, - "description-experimental-types": "", - "description-infinite-panning": "", - "description-inline-editing": "", - "description-pan-zoom": "", + "description-experimental-types": "Kísérleti elemtípusok kiválasztásának engedélyezése", + "description-infinite-panning": "Végtelen pásztázás engedélyezése – hasznos a tágas vásznakhoz. Figyelem: ez egy kísérleti funkció, és jelenleg csak a felül/bal oldalon korlátozott elemekkel működik jól", + "description-inline-editing": "A panel közvetlen szerkesztésének engedélyezése", + "description-pan-zoom": "Pásztázás és nagyítás engedélyezése", "direction-options": { - "label-both": "", - "label-forward": "", - "label-none": "", - "label-reverse": "" + "label-both": "Mindkettő", + "label-forward": "Előre", + "label-none": "Nincs", + "label-reverse": "Fordított" }, "drone-front-item": { - "category-drone-front": "", - "name-roll-angle": "" + "category-drone-front": "Drón eleje", + "name-roll-angle": "Billentési szög" }, "drone-side-item": { - "category-drone-side": "", - "name-pitch-angle": "" + "category-drone-side": "Drón oldala", + "name-pitch-angle": "Bólintási szög" }, "drone-top-item": { - "category-drone-top": "", - "name-back-left-rotor-rpm": "", - "name-back-right-rotor-rpm": "", - "name-front-left-rotor-rpm": "", - "name-front-right-rotor-rpm": "", - "name-yaw-angle": "" + "category-drone-top": "Drón teteje", + "name-back-left-rotor-rpm": "Bal hátsó rotor fordulatszáma", + "name-back-right-rotor-rpm": "Jobb hátsó rotor fordulatszáma", + "name-front-left-rotor-rpm": "Bal elülső rotor fordulatszáma", + "name-front-right-rotor-rpm": "Jobb elülső rotor fordulatszáma", + "name-yaw-angle": "Elfordulási szög" }, "element-editor": { - "category-layout": "", - "description-unsupported": "", - "name-quick-placement": "" + "category-layout": "Elrendezés", + "description-unsupported": "A kiválasztott elemtípust a jelenlegi beállítások nem támogatják. Engedélyezze a speciális elemtípusokat.", + "name-quick-placement": "Gyors elhelyezés" }, "ellipse-item": { - "category-ellipse": "", + "category-ellipse": "Ellipszis", "label": { "bottom": "Lent", "center": "Középre", @@ -3759,50 +3788,50 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } }, "icon-item": { - "category-icon": "", - "name-fill-color": "", - "name-svg-path": "" + "category-icon": "Ikon", + "name-fill-color": "Kitöltőszín", + "name-svg-path": "SVG-útvonal" }, "inline-edit": { "canvas-inline-editor": "Beágyazott vászonszerkesztő", "tooltip-close-inline-editor": "Beágyazott szerkesztő bezárása" }, "inline-edit-body": { - "category-selected-element": "", + "category-selected-element": "Kiválasztott elem ({{element}})", "label-add-item": "Elem hozzáadása", "please-select-an-element": "Válasszon egy elemet" }, - "label-actions": "", - "label-color": "", - "label-direction": "", - "label-image": "", - "label-image-size": "", - "label-line-style": "", - "label-links": "", - "label-radius": "", - "label-size": "", - "label-width": "", + "label-actions": "Műveletek", + "label-color": "Szín", + "label-direction": "Irány", + "label-image": "Kép", + "label-image-size": "Kép mérete", + "label-line-style": "Vonal stílusa", + "label-links": "Hivatkozások", + "label-radius": "Sugár", + "label-size": "Méret", + "label-width": "Szélesség", "layer-editor": { - "category-layer": "", - "category-layout": "", - "name-constraints": "", - "name-elements": "" + "category-layer": "Réteg", + "category-layout": "Elrendezés", + "name-constraints": "Korlátozások", + "name-elements": "Elemek" }, "line-style-editor": { "label-animate": "Animálás" }, "line-style-options": { - "label-solid": "" + "label-solid": "Folytonos" }, "metric-value-item": { "label": { @@ -3817,15 +3846,15 @@ "auto": "Automatikus" } }, - "name-align-text": "", - "name-color": "", - "name-experimental-types": "", - "name-infinite-panning": "", - "name-inline-editing": "", - "name-pan-zoom": "", - "name-text": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-color": "Szöveg színe", + "name-experimental-types": "Kísérleti elemtípusok", + "name-infinite-panning": "Végtelen pásztázás", + "name-inline-editing": "Beágyazott szerkesztés", + "name-pan-zoom": "Pásztázás és nagyítás", + "name-text": "Szöveg", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "not-found-display": { "not-found": "<0>Nem található: " }, @@ -3838,7 +3867,7 @@ "zoom-scroll-wheel": "Nagyítás: egérgörgő" }, "parallelogram-item": { - "category-parallelogram": "", + "category-parallelogram": "Paralelogramma", "label": { "bottom": "Lent", "center": "Középre", @@ -3847,11 +3876,11 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } @@ -3864,21 +3893,21 @@ }, "placement-editor": { "horizontal-options": { - "label-center": "", - "label-left": "", - "label-left-and-right": "", - "label-right": "", - "label-scale": "" + "label-center": "Középre", + "label-left": "Balra", + "label-left-and-right": "Bal és jobb", + "label-right": "Jobbra", + "label-scale": "Skála" }, "label-constraints": "Korlátozások", "label-position": "Helyzet", "loading": "Betöltés…", "vertical-options": { - "label-bottom": "", - "label-center": "", - "label-scale": "", - "label-top": "", - "label-top-and-bottom": "" + "label-bottom": "Lent", + "label-center": "Középre", + "label-scale": "Skála", + "label-top": "Felül", + "label-top-and-bottom": "Felül és alul" } }, "quick-positioning": { @@ -3890,7 +3919,7 @@ "tooltip-align-vertical-centers": "Igazítás függőleges középre" }, "rectangle-item": { - "category-rectangle": "", + "category-rectangle": "Téglalap", "label": { "bottom": "Lent", "center": "Középre", @@ -3899,33 +3928,33 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } }, "server-item": { - "category-server": "", - "name-blink-rate": "", - "name-bulb-color": "", - "name-status-color": "", - "name-type": "", + "category-server": "Kiszolgáló", + "name-blink-rate": "Villogási sebesség [hz] (0 = ki)", + "name-bulb-color": "Lámpa színe", + "name-status-color": "Állapot színe", + "name-type": "Típus", "type-options": { - "label-database": "", - "label-single": "", - "label-stack": "", - "label-terminal": "" + "label-database": "Adatbázis", + "label-single": "Különálló", + "label-stack": "Stack", + "label-terminal": "Terminál" } }, "text-display": { "double-click-to-set": "Kattintson duplán a szöveg beállításához" }, "text-item": { - "category-text": "", + "category-text": "Szöveg", "label": { "bottom": "Lent", "center": "Középre", @@ -3934,11 +3963,11 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } @@ -3960,7 +3989,7 @@ "tooltip-remove": "Eltávolítás" }, "triangle-item": { - "category-triangle": "", + "category-triangle": "Háromszög", "label": { "bottom": "Lent", "center": "Középre", @@ -3969,18 +3998,18 @@ "right": "Jobbra", "top": "Felső" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Szöveg igazítása", + "name-text": "Szöveg", + "name-text-color": "Szöveg színe", + "name-text-size": "Szöveg mérete", + "name-vertical-align": "Függőleges igazítás", "placeholder": { "auto": "Automatikus" } }, "wind-turbine-item": { - "category-wind-turbine": "", - "name-rpm": "" + "category-wind-turbine": "Szélturbina", + "name-rpm": "Fordulatszám" } }, "carousel": { @@ -3997,8 +4026,8 @@ "close-button": { "tooltip": "Bezárás" }, - "cloud-enterprise-feature-badge": "", - "cloud-feature-badge": "", + "cloud-enterprise-feature-badge": "Felhő és vállalati", + "cloud-feature-badge": "Felhő", "combobox": { "async": { "error": "Hiba történt az opciók betöltése során." @@ -4528,7 +4557,7 @@ } }, "description": { - "action": "" + "action": "Irányítópult leírásának módosítása" }, "dynamic-config-value-editor": { "render-label": { @@ -4537,12 +4566,16 @@ }, "edit-actions": { "add": "{{typeName}} hozzáadása", - "move": "", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", + "move": "{{typeName}} áthelyezése", "panel-background": "Panel hátterének módosítása", "panel-description": "Panel leírásának módosítása", - "panel-max-repeats-per-row": "", + "panel-max-repeats-per-row": "Max. ismétlés soronként", "panel-repeat-direction": "Ismétlési irány", - "panel-repeat-variable": "", + "panel-repeat-variable": "Panelismétlés alapja:", "panel-title": "Panel címének módosítása", "paste-panel": "Panel beillesztése", "remove": "{{typeName}} eltávolítása", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Irányítópult", + "element": "", "local-variable": "Helyi változó", "multiple-elements": "Több elem", "multiple-elements-delete-text": "Biztosan törli ezeket az elemeket?", @@ -4676,7 +4710,7 @@ "get-field-override-categories": { "label-add-field-override": "Mezőfelülbírálás hozzáadása", "label-add-override-property": "Felülbírálási tulajdonság hozzáadása", - "override-name": "", + "override-name": "{{overrideNum}} felülbírálása", "title": { "add-button": "gomb hozzáadása" } @@ -4901,7 +4935,7 @@ "title-matched_other": "{{totalCount}}/{{count}} egyező lehetőség" }, "outline": { - "repeated-item": "", + "repeated-item": "Ismétlés", "tree-item": { "empty": "(üres)", "no-title": "" @@ -5059,8 +5093,8 @@ "label-changes": "Módosítások", "label-details": "Részletek", "title": "Irányítópult mentése", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Irányítópult-másolat mentése", + "title-provisioned": "Kiépített irányítópult" }, "save-dashboard-error-proxy": { "body-name-exists": "Egy irányítópult már létezik ugyanazzal a névvel a kijelölt mappában.<1><2>Biztosan menti ezt az irányítópultot?", @@ -5137,7 +5171,7 @@ } }, "title": { - "action": "" + "action": "Irányítópult címének módosítása" }, "toolbar": { "add": "Hozzáadás", @@ -5572,12 +5606,12 @@ } }, "delete-provisioned-dashboard-form": { - "api-error": "", + "api-error": "Az irányítópult törlése nem sikerült", "cancel-action": "Mégse", "delete-action": "Irányítópult törlése", - "delete-read-only-file-message": "", + "delete-read-only-file-message": "Ez az irányítópult nem törölhető közvetlenül a Grafanából, mert az adattár csak olvasható. Az irányítópult törléséhez távolítsa el a fájlt a Git-tárból.", "deleting": "Törlés...", - "drawer-title": "", + "drawer-title": "Kiépített irányítópult törlése", "title-this-repository-is-read-only": "Ez az adattár csak olvasható" }, "description-label": { @@ -5591,7 +5625,7 @@ } }, "edit-provisioned-dashboard-form": { - "success": "" + "success": "Irányítópult-módosítások mentése sikeres" }, "email-list": { "aria-label-emailmenu": "E-mail-menü ki- és bekapcsolása" @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5785,7 +5818,7 @@ } }, "panel-data-alerting-tab": { - "tab-label": "" + "tab-label": "Riasztás" }, "panel-data-alerting-tab-rendered": { "alert": { @@ -5796,14 +5829,14 @@ "title-dashboard-not-saved": "Az irányítópult nincs mentve" }, "panel-data-queries-tab": { - "tab-label": "" + "tab-label": "Lekérdezések" }, "panel-data-queries-tab-rendered": { "add-query": "Lekérdezés hozzáadása", "expression": "Kifejezés " }, "panel-data-transformations-tab": { - "tab-label": "" + "tab-label": "Transzformációk" }, "panel-data-transformations-tab-rendered": { "add-another-transformation": "Másik transzformáció hozzáadása", @@ -5906,8 +5939,8 @@ "label-changes": "Módosítások", "label-details": "Részletek", "title": "Irányítópult mentése", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Irányítópult-másolat mentése", + "title-provisioned": "Kiépített irányítópult" } }, "save-dashboard-form": { @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Valaki más frissítette ezt az irányítópultot", "would-still-dashboard": "Biztosan menti ezt az irányítópultot?" }, - "schema-v2-library-panels-export": "A dinamikus irányítópult funkcionalitása kísérleti jellegű, és nem rendelkezik teljes funkcionalitással a jelenlegi irányítópultok viselkedéséhez képest. Új sémaformátumon alapul, amely nem támogatja a könyvtárpaneleket. Ez azt jelenti, hogy amikor az irányítópultot egy másik példányban való használathoz exportálja, nem fogja tartalmazni a könyvtárpaneleket. Támogatni kívánjuk őket a funkció <2>életciklusának előrehaladtával.", - "schema-v2-library-panels-export-title": "A Dashboard Schema V2 még nem támogatja a könyvtárpanelek exportálását egy másik példányban való használathoz", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Irányítópult visszafordíthatatlanul megváltoztatva" }, "save-dashboard-form-common-options": { @@ -6231,18 +6264,18 @@ } }, "dashlist": { - "category-dashboard-list": "", - "name-folder": "", - "name-include-current-template-variables": "", - "name-include-current-time-range": "", - "name-max-items": "", - "name-query": "", - "name-recently-viewed": "", - "name-search": "", - "name-show-folder-names": "", - "name-show-headings": "", - "name-starred": "", - "name-tags": "" + "category-dashboard-list": "Irányítópult-lista", + "name-folder": "Mappa", + "name-include-current-template-variables": "Aktuális sablonváltozó értékeinek belefoglalása", + "name-include-current-time-range": "Aktuális időtartomány belefoglalása", + "name-max-items": "Elemek max. száma", + "name-query": "Lekérdezés", + "name-recently-viewed": "Legutóbb megtekintett", + "name-search": "Keresés", + "name-show-folder-names": "Mappanevek megjelenítése", + "name-show-headings": "Címsorok megjelenítése", + "name-starred": "Csillagozott", + "name-tags": "Címkék" }, "data-source-list": { "empty-state": { @@ -6504,15 +6537,15 @@ "dimensions": { "background-size-editor": { "options": { - "label-contain": "", - "label-cover": "", - "label-fill": "", - "label-original": "", - "label-tile": "" + "label-contain": "Tartalmaz", + "label-cover": "Lefedés", + "label-fill": "Kitöltés", + "label-original": "Eredeti", + "label-tile": "Csempe" } }, "color-dimension-editor": { - "label-fixed-color": "", + "label-fixed-color": "Rögzített szín", "noOptionsMessage-no-fields-found": "" }, "file-dropzone-custom-children": { @@ -6527,10 +6560,10 @@ "placeholder-search": "Keresés" }, "resource-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Karakterláncmező-eredmény használata", + "description-fixed": "Rögzített érték", "label-field": "Mező", - "label-fixed": "", + "label-fixed": "Rögzített", "label-mappings": "Hozzárendelések", "label-source": "Forrás" }, @@ -6547,16 +6580,16 @@ }, "scalar-dimension-editor": { "fixed-value-options": { - "label-fixed-values": "" + "label-fixed-values": "Rögzített érték" }, "label-limit": "Korlát", "label-value": "Érték", "noOptionsMessage-no-fields-found": "", "scalar-options": { - "description-clamped": "", - "description-mod": "", - "label-clamped": "", - "label-mod": "" + "description-clamped": "Mezőértékek használata, maximumhoz és minimumhoz kapcsolva", + "description-mod": "Mezőértékek használata, mod. és max. között", + "label-clamped": "Összekapcsolt", + "label-mod": "Mod." } }, "scale-dimension-editor": { @@ -6571,10 +6604,10 @@ "noOptionsMessage-no-fields-found": "" }, "text-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Mezőérték megjelenítése", + "description-fixed": "Rögzített érték", "label-field": "Mező", - "label-fixed": "", + "label-fixed": "Rögzített", "label-source": "Forrás", "label-template": "Sablon", "label-value": "Érték" @@ -6588,16 +6621,16 @@ "description-percentage-means-thresholds-relative": "A százalék a minimumhoz és maximumhoz viszonyított küszöbértékeket jelenti", "modes": { "description": { - "threshold-based-percent-between-minmax": "", - "thresholds-based-absolute-values": "" + "threshold-based-percent-between-minmax": "Válassza ki a küszöbértéket a min./max. közötti százalék alapján", + "thresholds-based-absolute-values": "Válassza ki a küszöbértékeket az abszolút értékek alapján" }, "label": { - "absolute": "", - "percentage": "" + "absolute": "Abszolút", + "percentage": "Százalék" } }, "thresholds-mode": "Küszöbértékek mód", - "value-base": "" + "value-base": "Alap" }, "urlpicker-tab": { "label-preview": "Előnézet" @@ -7131,6 +7164,7 @@ "start-time": "Kezdés időpontja:" } }, + "share-span": "", "warnings": "Figyelmeztetések" }, "span-filters": { @@ -7398,23 +7432,23 @@ "title": "Kilép az oldalról?" }, "gauge": { - "category-gauge": "", - "description-min-height": "", - "description-min-width": "", - "description-neutral": "", - "description-show-threshold-labels": "", - "description-show-threshold-markers": "", + "category-gauge": "Mérőeszköz", + "description-min-height": "Minimális sormagasság (vízszintes tájolás)", + "description-min-width": "Minimális oszlopszélesség (függőleges tájolás)", + "description-neutral": "Hagyja üresen, hogy a Min. értéket használja semleges pontként", + "description-show-threshold-labels": "A küszöbértékek megjelenítése a mérősáv körül", + "description-show-threshold-markers": "A küszöbértékeket külső sávként jeleníti meg", "gauge-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Automatikus", + "label-manual": "Kézi" }, - "name-gauge-size": "", - "name-min-height": "", - "name-min-width": "", - "name-neutral": "", - "name-show-threshold-labels": "", - "name-show-threshold-markers": "", - "placeholder-neutral": "" + "name-gauge-size": "Mérőeszköz mérete", + "name-min-height": "Min. magasság", + "name-min-width": "Min. szélesség", + "name-neutral": "Semleges", + "name-show-threshold-labels": "Küszöbértékcímkék megjelenítése", + "name-show-threshold-markers": "Küszöbértékjelölők megjelenítése", + "placeholder-neutral": "automatikus" }, "gen-ai": { "apply-suggestion": "Alkalmaz", @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7748,85 +8185,85 @@ }, "builder": { "axis": { - "category-axis": "", - "color-label": "", + "category-axis": "Tengely", + "color-label": "Szín", "color-options": { - "label-series": "", - "label-text": "" + "label-series": "Sorozat", + "label-text": "Szöveg" }, "grid-line-options": { - "label-auto": "", - "label-off": "", - "label-on": "" + "label-auto": "Automatikus", + "label-off": "Ki", + "label-on": "Be" }, - "name-centered-zero": "", - "name-grid-lines": "", - "name-label": "", - "name-placement": "", - "name-scale": "", - "name-show-border": "", - "name-soft-max": "", - "name-soft-min": "", - "name-width": "", - "placeholder-label": "", - "placeholder-soft-max": "", - "placeholder-soft-min": "", - "placeholder-width": "", + "name-centered-zero": "Középre igazított nulla", + "name-grid-lines": "Rácsvonalak megjelenítése", + "name-label": "Címke", + "name-placement": "Elhelyezés", + "name-scale": "Skála", + "name-show-border": "Szegély megjelenítése", + "name-soft-max": "Nem kényszerítő max.", + "name-soft-min": "Nem kényszerítő min.", + "name-width": "Szélesség", + "placeholder-label": "Opcionális szöveg", + "placeholder-soft-max": "Lásd: Standard beállítások > Max.", + "placeholder-soft-min": "Lásd: Standard beállítások > Min.", + "placeholder-width": "Automatikus", "scale-distribution-editor": { "distribution-options": { - "label-linear": "", - "label-log": "", - "label-symlog": "" + "label-linear": "Lineáris", + "label-log": "Logaritmikus", + "label-symlog": "Symlog" } } }, "legend": { - "category": "", - "description-values": "", + "category": "Jelmagyarázat", + "description-values": "Válassza ki a jelmagyarázatban megjeleníteni kívánt értékeket vagy számításokat", "mode-options": { - "label-list": "", - "label-table": "" + "label-list": "Lista", + "label-table": "Táblázat" }, - "name-mode": "", - "name-placement": "", - "name-values": "", - "name-visibility": "", - "name-width": "", + "name-mode": "Mód", + "name-placement": "Elhelyezés", + "name-values": "Értékek", + "name-visibility": "Láthatóság", + "name-width": "Szélesség", "placement-options": { - "label-bottom": "", - "label-right": "" + "label-bottom": "Lent", + "label-right": "Jobbra" } }, "stacking": { - "name-stack-series": "" + "name-stack-series": "Stacksorozat" }, "text": { - "category-text-size": "", - "name-percent-change": "", - "name-title": "", - "name-value": "", - "placeholder-percent-change": "", - "placeholder-title": "", - "placeholder-value": "" + "category-text-size": "Szöveg mérete", + "name-percent-change": "Változások százalékos aránya", + "name-title": "Cím", + "name-value": "Érték", + "placeholder-percent-change": "Automatikus", + "placeholder-title": "Automatikus", + "placeholder-value": "Automatikus" }, "tooltip": { - "category": "", - "description-hover-proximity": "", + "category": "Elemleírás", + "description-hover-proximity": "A kurzor képpontban megadott távolsága egy ponttól az elemleírás aktiválásához", "modeOptions": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Összes", + "label-hidden": "Rejtett", + "label-single": "Különálló" }, - "name-hide-zeros": "", - "name-hover-proximity": "", - "name-max-height": "", - "name-max-width": "", - "name-tooltip-mode": "", - "name-values-sort-order": "", + "name-hide-zeros": "Nullák elrejtése", + "name-hover-proximity": "Rámutatási közelség", + "name-max-height": "Max. magasság", + "name-max-width": "Max. szélesség", + "name-tooltip-mode": "Elemleírási mód", + "name-values-sort-order": "Értékek rendezési sorrendje", "sortOptions": { - "label-ascending": "", - "label-descending": "", - "label-none": "" + "label-ascending": "Növekvő", + "label-descending": "Csökkenő", + "label-none": "Nincs" } } }, @@ -7949,6 +8386,60 @@ "previous-label": "Előző hónap", "select-placeholder": "Dátum és idő kijelölése" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Bezárás" }, @@ -7988,19 +8479,33 @@ "tooltip-expand-all": "Minden sor kibontása" }, "matchers-ui": { - "description-field-name-by-regex-matcher": "", - "description-fields-by-query": "", - "description-fields-with-name": "", - "description-fields-with-type": "", - "description-fields-with-value": "", + "description-field-name-by-regex-matcher": "Tulajdonságok beállítása a reguláris kifejezésnek megfelelő nevű mezők számára", + "description-fields-by-query": "Tulajdonságok beállítása egy adott lekérdezés mezői számára", + "description-fields-with-name": "Tulajdonságok beállítása egy adott mező számára", + "description-fields-with-type": "Tulajdonságok beállítása adott típusú mezők számára (szám, karakterlánc, logikai érték)", + "description-fields-with-value": "Tulajdonságok beállítása a szűkítőfeltétellel rendelkező mezők számára", "field-name-picker": { - "placeholder": "" + "placeholder": "Mező kijelölése" }, - "name-field-name-by-regex-matcher": "", - "name-fields-by-query": "", - "name-fields-with-name": "", - "name-fields-with-type": "", - "name-fields-with-value": "" + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, + "name-field-name-by-regex-matcher": "Reguláris kifejezésnek megfelelő nevű mezők", + "name-fields-by-query": "A lekérdezés által visszaadott mezők", + "name-fields-with-name": "Mezők névvel", + "name-fields-with-type": "Mezők típussal", + "name-fields-with-value": "Mezők értékekkel" }, "menu-item": { "keyboard-shortcut-label": "Billentyűparancs" @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Nincsenek értékek", - "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} / {{numRows}} sor" + "pagination-summary": "{{itemsRangeStart}} – {{displayedEnd}} / {{numRows}} sor", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Címkék" @@ -8124,55 +8632,55 @@ "config": { "get-graph-field-options": { "axis-placement": { - "description-auto": "", - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-right": "" + "description-auto": "Az első mező balra, minden más jobbra", + "label-auto": "Automatikus", + "label-hidden": "Rejtett", + "label-left": "Balra", + "label-right": "Jobbra" }, "bar-alignment": { - "description-after": "", - "description-before": "", - "description-center": "" + "description-after": "Utána", + "description-before": "Előtte", + "description-center": "Középre" }, "draw-style": { - "label-bars": "", - "label-lines": "", - "label-points": "" + "label-bars": "Sávok", + "label-lines": "Vonalak", + "label-points": "Pontok" }, "fill-gradient": { - "description-hue": "", - "description-opacity": "", - "description-scheme": "", - "label-hue": "", - "label-none": "", - "label-opacity": "", - "label-scheme": "" + "description-hue": "Kis színátmenet", + "description-opacity": "Kitöltési átlátszósági átmenet engedélyezése", + "description-scheme": "Színséma használata az átmenet meghatározásához", + "label-hue": "Árnyalat", + "label-none": "Nincs", + "label-opacity": "Átlátszóság", + "label-scheme": "Séma" }, "line-interpolation": { - "description-linear": "", - "description-smooth": "", - "description-step-after": "", - "description-step-before": "" + "description-linear": "Lineáris", + "description-smooth": "Sima", + "description-step-after": "Következő lépés", + "description-step-before": "Előző lépés" }, "show-points": { - "description-auto": "", - "label-always": "", - "label-auto": "", - "label-never": "" + "description-auto": "Pontok megjelenítése alacsony sűrűség esetén", + "label-always": "Mindig", + "label-auto": "Automatikus", + "label-never": "Soha" }, "stacking": { - "label-100": "", - "label-normal": "", - "label-off": "" + "label-100": "100%", + "label-normal": "Normál", + "label-off": "Ki" }, "thresholds-display-mode": { - "label-dashed-lines": "", - "label-filled-regions": "", - "label-filled-regions-and-dashed-lines": "", - "label-filled-regions-and-lines": "", - "label-lines": "", - "label-off": "" + "label-dashed-lines": "Vonalakként (szaggatott)", + "label-filled-regions": "Kitöltött területekként", + "label-filled-regions-and-dashed-lines": "Kitöltött területekként és vonalakként (szaggatott)", + "label-filled-regions-and-lines": "Kitöltött területekként és vonalakként", + "label-lines": "Vonalakként", + "label-off": "Ki" } } } @@ -8219,78 +8727,78 @@ }, "heatmap": { "calculate-from-data-options": { - "label-no": "", - "label-yes": "" + "label-no": "Nem", + "label-yes": "Igen" }, - "category-cell-display": "", - "category-colors": "", - "category-exemplars": "", - "category-heatmap": "", - "category-legend": "", - "category-tooltip": "", - "category-y-axis": "", + "category-cell-display": "Cella megjelenítése", + "category-colors": "Színek", + "category-exemplars": "Mintapéldányok", + "category-heatmap": "Hőtérkép", + "category-legend": "Jelmagyarázat", + "category-tooltip": "Elemleírás", + "category-y-axis": "Y tengely", "mode-options": { - "label-opacity": "", - "label-scheme": "" + "label-opacity": "Átlátszóság", + "label-scheme": "Séma" }, - "name-axis-label": "", - "name-axis-width": "", - "name-calculate-from-data": "", - "name-cell-gap": "", - "name-color": "", - "name-decimals": "", - "name-end-color-at-value": "", - "name-exponent": "", - "name-hide-cells-gt": "", - "name-hide-cells-lt": "", - "name-max-height": "", - "name-max-value": "", - "name-max-width": "", - "name-min-value": "", - "name-mode": "", - "name-placement": "", - "name-reverse": "", - "name-scale": "", - "name-scheme": "", - "name-show-color-scale": "", - "name-show-histogram": "", - "name-show-legend": "", - "name-start-color-from-value": "", - "name-steps": "", - "name-tick-alignment": "", - "name-tooltip-mode": "", - "name-unit": "", - "name-value-name": "", - "name-y-axis-scale": "", - "placeholder-axis-label": "", - "placeholder-axis-width": "", - "placeholder-decimals": "", - "placeholder-end-color-at-value": "", - "placeholder-hide-cells-gt": "", - "placeholder-hide-cells-lt": "", - "placeholder-max-value": "", - "placeholder-min-value": "", - "placeholder-start-color-from-value": "", - "placeholder-value-name": "", + "name-axis-label": "Tengelycímke", + "name-axis-width": "Tengely szélessége", + "name-calculate-from-data": "Számítás az adatokból", + "name-cell-gap": "Cellaköz", + "name-color": "Szín", + "name-decimals": "Tizedesjegyek", + "name-end-color-at-value": "Színskála vége az értéknél", + "name-exponent": "Exponens", + "name-hide-cells-gt": "Cellák elrejtése >= értékkel", + "name-hide-cells-lt": "Cellák elrejtése <= értékkel", + "name-max-height": "Max. magasság", + "name-max-value": "Max. érték", + "name-max-width": "Max. szélesség", + "name-min-value": "Min. érték", + "name-mode": "Mód", + "name-placement": "Elhelyezés", + "name-reverse": "Fordított", + "name-scale": "Skála", + "name-scheme": "Séma", + "name-show-color-scale": "Színskála megjelenítése", + "name-show-histogram": "Hisztogram megjelenítése (Y tengely)", + "name-show-legend": "Jelmagyarázat megjelenítése", + "name-start-color-from-value": "Színskála kezdete az értéktől", + "name-steps": "Lépések", + "name-tick-alignment": "Feliratok igazítása", + "name-tooltip-mode": "Elemleírási mód", + "name-unit": "Egység", + "name-value-name": "Érték neve", + "name-y-axis-scale": "Y tengely skálája", + "placeholder-axis-label": "Automatikus", + "placeholder-axis-width": "Automatikus", + "placeholder-decimals": "Automatikus", + "placeholder-end-color-at-value": "Automatikus (max.)", + "placeholder-hide-cells-gt": "Nincs", + "placeholder-hide-cells-lt": "Nincs", + "placeholder-max-value": "Automatikus", + "placeholder-min-value": "Automatikus", + "placeholder-start-color-from-value": "Automatikus (min.)", + "placeholder-value-name": "Érték", "placement-options": { - "label-hidden": "", - "label-left": "", - "label-right": "" + "label-hidden": "Rejtett", + "label-left": "Balra", + "label-right": "Jobbra" }, "scale-options": { - "label-exponential": "", - "label-linear": "" + "label-exponential": "Exponenciális", + "label-linear": "Lineáris" }, "tick-alignment-options": { - "label-auto": "", - "label-bottom": "", - "label-middle": "", - "label-top": "" + "label-auto": "Automatikus", + "label-bottom": "Alsó (GE)", + "label-middle": "Középső", + "label-top": "Felső (LE)" }, "tooltip-mode-options": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Összes", + "label-hidden": "Rejtett", + "label-single": "Különálló" } }, "help-modal": { @@ -8350,14 +8858,14 @@ "troubleshooting-help": "Hibaelhárítási segítség kéréséhez küldjön egy pillanatfelvételt erről a panelről a Grafana Labs műszaki ügyfélszolgálatának. A pillanatfelvétel lekérdezési válaszadatokat és panelbeállításokat tartalmaz." }, "histogram": { - "category-histogram": "", + "category-histogram": "Hisztogram", "histogram-panel": { "no-histogram-found-in-response": "Nem található hisztogram a válaszban" }, - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-width": "", - "name-stacking": "" + "name-fill-opacity": "Kitöltési átlátszóság", + "name-gradient-mode": "Színátmenetes mód", + "name-line-width": "Vonal szélessége", + "name-stacking": "Egymásra helyezés" }, "inspector": { "inspect-data-tab": { @@ -8738,16 +9246,16 @@ } }, "logs": { - "category-logs": "", + "category-logs": "Naplók", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Pontos", + "label-none": "Nincs", + "label-numbers": "Számok", + "label-signature": "Aláírás" }, - "description-enable-infinite-scrolling": "", - "description-enable-syntax-highlighting": "", - "description-show-controls": "", + "description-enable-infinite-scrolling": "Kísérleti. További eredmények kéréséhez görgessen a naplók listájának aljára.", + "description-enable-syntax-highlighting": "Használjon előre definiált szintaxisszínező grammatikát a naplósorok releváns részeinek kiemeléséhez", + "description-show-controls": "Vezérlők megjelenítése az utolsó vagy az első naplósorra ugráshoz és szűrés naplószint szerint", "fields": { "type": { "loki": { @@ -8761,8 +9269,8 @@ } }, "font-size-options": { - "label-default": "", - "label-small": "" + "label-default": "Alapértelmezett", + "label-small": "Kicsi" }, "get-dataframe-fields": { "vars": { @@ -8809,58 +9317,64 @@ "tooltip-error": "Hiba: {{errorMessage}}" }, "log-line-details": { - "clear-search": "", - "close": "", - "copy-shortlink": "", - "copy-to-clipboard": "", + "clear-search": "Törlés", + "close": "Napló részleteinek bezárása", + "copy-shortlink": "Rövid hivatkozás másolása", + "copy-to-clipboard": "Másolás vágólapra", "displayed-fields-section": "", "fields": { - "adhoc-statistics": "", - "copy-value-to-clipboard": "", - "filter-out": "", - "filter-out-query": "", + "adhoc-statistics": "Eseti statisztikák", + "copy-value-to-clipboard": "Érték másolása a vágólapra", + "filter-out": "Érték kiszűrése", + "filter-out-query": "Érték kiszűrése a(z) {{query}} lekérdezésben", "toggle-field-button": { - "field-instead-message": "", - "hide-this-field": "" + "field-instead-message": "Ezen mező megjelenítése az üzenet helyett", + "hide-this-field": "Ezen mező elrejtése" } }, - "fields-section": "", - "hide-log-line": "", + "fields-section": "Mezők", + "hide-log-line": "Naplósor elrejtése", "inline-mode": "", - "links-section": "", + "links-section": "Hivatkozások", "log-line-field": "", - "log-line-section": "", + "log-line-section": "Naplósor", "move-displayed-field-down": "", "move-displayed-field-up": "", - "no-details": "", - "pin-line": "", + "no-details": "Nincs megjeleníthető mező.", + "open-assistant": "", + "pin-line": "Napló kitűzése", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { - "no-results": "" + "no-results": "Nincs megjeleníthető találat." }, - "search-placeholder": "", - "show-context": "", - "show-log-line": "", + "search-placeholder": "Mezőnevek és értékek keresése", + "show-context": "Kontextus megjelenítése", + "show-log-line": "Naplósor megjelenítése", "sidebar-mode": "", - "unpin-line": "" + "unpin-line": "Naplókitűzés feloldása" }, "log-line-menu": { "copy-link": "Hivatkozás másolása a naplósorba", "copy-log": "Naplósor másolása", "hide-details": "Napló részleteinek megjelenítése", "icon-label": "Naplómenü", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Napló rögzítése", "show-context": "Kontextus megjelenítése", "show-details": "Napló részleteinek elrejtése", "unpin-from-outline": "Naplórögzítés feloldása" }, "log-list-search": { - "close": "", - "filter": "", - "info": "", - "input-placeholder": "", - "next": "", - "prev": "" + "close": "Keresés bezárása", + "filter": "Megfelelő naplók szűrése", + "info": "Karakterláncok keresése a kliensoldalon a megjelenített naplókban. Nem tévesztendő össze a lekérdezési szűrőkkel. Használja ezt az összetevőt adott karakterláncok kereséséhez a naplóeredményekben.", + "input-placeholder": "Keresés a naplókban", + "next": "Következő eredmény", + "prev": "Előző eredmény" }, "log-row-context-modal": { "error-loading-log-more-logs": "Hiba a további naplók betöltésekor.", @@ -8915,7 +9429,7 @@ "escape-newlines": "Helytelenül értelmezett újsor- és tabulátorkarakteres szekvenciák javítása a naplósorokban", "font-size-default": "Kis betűméret használata", "font-size-small": "Alapértelmezett betűméret használata", - "hide-search": "", + "hide-search": "Keresés bezárása", "hide-timestamps": "Időbélyegek elrejtése", "hide-unique-labels": "Egyedi címkék elrejtése", "newest-first": "Rendezés a legújabb naplók szerint – kattintson, hogy a legrégebbi naplók jelenjenek meg elsőként", @@ -8924,7 +9438,7 @@ "remove-escaping": "Módosított értelmezés eltávolítása", "scroll-bottom": "Görgetés az aljára", "scroll-top": "Görgetés a tetejére", - "show-search": "", + "show-search": "Keresés a naplóeredményekben", "show-timestamps": "Időbélyegek megjelenítése", "show-unique-labels": "Egyedi címkék megjelenítése", "unwrap-lines": "Sortörés megszüntetése", @@ -8942,26 +9456,26 @@ "common-labels": "Közös címkék:" } }, - "name-common-labels": "", - "name-deduplication": "", + "name-common-labels": "Közös címkék", + "name-deduplication": "Duplikátumok törlése", "name-details-mode": "", "name-details-options": { "label-inline": "", "label-sidebar": "" }, - "name-enable-infinite-scrolling": "", - "name-enable-log-details": "", - "name-enable-syntax-highlighting": "", - "name-font-size": "", - "name-order": "", - "name-prettify-json": "", - "name-show-controls": "", - "name-time": "", - "name-unique-labels": "", - "name-wrap-lines": "", + "name-enable-infinite-scrolling": "Végtelen görgetés engedélyezése", + "name-enable-log-details": "Napló részleteinek engedélyezése", + "name-enable-syntax-highlighting": "Szintaxiskiemelés engedélyezése", + "name-font-size": "Betűméret", + "name-order": "Sorrend", + "name-prettify-json": "JSON szépítése", + "name-show-controls": "Vezérlőelemek megjelenítése", + "name-time": "Idő", + "name-unique-labels": "Egyedi címkék", + "name-wrap-lines": "Sortörés", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Újabbat előre", + "label-oldest-first": "Régebbit előre" }, "out-of-range-message": { "end-of-the-selected-time-range": "A kijelölt időtartomány vége." @@ -8975,7 +9489,7 @@ "un-themed-log-details": { "aria-label-data-links": "Adathivatkozások", "aria-label-fields": "Mezők", - "aria-label-line": "", + "aria-label-line": "Naplósor", "aria-label-log-level": "Naplózási szint", "aria-label-no-details": "Nincsenek részletek" }, @@ -8996,27 +9510,27 @@ } }, "logs-new": { - "category-logs": "", + "category-logs": "Naplók", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Pontos", + "label-none": "Nincs", + "label-numbers": "Számok", + "label-signature": "Aláírás" }, - "description-infinite-scrolling": "", - "description-show-controls": "", - "description-syntax-highlighting": "", - "name-deduplication": "", - "name-enable-log-details": "", - "name-infinite-scrolling": "", - "name-order": "", - "name-show-controls": "", - "name-syntax-highlighting": "", - "name-time": "", - "name-wrap-lines": "", + "description-infinite-scrolling": "Kísérleti. További eredmények kéréséhez görgessen a naplók listájának aljára.", + "description-show-controls": "Vezérlők megjelenítése az utolsó vagy az első naplósorra ugráshoz és szűrés naplószint szerint", + "description-syntax-highlighting": "Használjon előre definiált szintaxisszínező grammatikát a naplósorok releváns részeinek kiemeléséhez", + "name-deduplication": "Duplikátumok törlése", + "name-enable-log-details": "Napló részleteinek engedélyezése", + "name-infinite-scrolling": "Végtelen görgetés engedélyezése", + "name-order": "Sorrend", + "name-show-controls": "Vezérlőelemek megjelenítése", + "name-syntax-highlighting": "Szintaxiskiemelés engedélyezése", + "name-time": "Idő", + "name-wrap-lines": "Sortörés", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Újabbat előre", + "label-oldest-first": "Régebbit előre" } }, "manage-dashbaords": { @@ -9682,7 +10196,9 @@ "aria-label": "Súgó" }, "invite-user": { - "invite-new-member-button": "Új tag meghívása" + "invite-button": "Meghívás", + "invite-new-member-button": "Új tag meghívása", + "invite-tooltip": "" }, "item": { "add-bookmark": "Hozzáadás könyvjelzőkhöz", @@ -9714,12 +10230,12 @@ "rss-button": "Újdonságok a blogról" }, "news": { - "category-news": "", - "description-show-image": "", - "description-url": "", + "category-news": "Hírek", + "description-show-image": "Azt vezérli, hogy a szöveges tartalom felett megjelenik-e a hírelem (og:image) közösségi médiás képe", + "description-url": "Támogatja az RSS- és Atom-hírcsatornákat", "link-title": "Tovább a Grafana labs blogjára", - "name-show-image": "", - "name-url": "", + "name-show-image": "Kép megjelenítése", + "name-url": "URL-cím", "news-panel": { "body-error-loading-rss-feed": "Győződjön meg arról, hogy a hírfolyam URL-címe helyes, és hogy a CORS helyesen van konfigurálva a kiszolgálón. Lásd: <2>Hírpanel dokumentációja.", "loading": "Betöltés…", @@ -9728,27 +10244,27 @@ "title": "Újdonságok a blogról" }, "node-graph": { - "category-edges": "", - "category-node-graph": "", - "category-nodes": "", + "category-edges": "Élek", + "category-node-graph": "Csomópontdiagram", + "category-nodes": "Csomópontok", "layout-algorithm-options": { - "description-force": "", - "description-grid": "", - "description-layered": "", - "label-force": "", - "label-grid": "", - "label-layered": "" + "description-force": "Erővektoros elrendezés", + "description-grid": "Rácsos elrendezés", + "description-layered": "Réteges elrendezés", + "label-force": "Erő", + "label-grid": "Rács", + "label-layered": "Réteges" }, - "name-arc-sections": "", - "name-layout-algorithm": "", - "name-main-stat-unit": "", - "name-secondary-stat-unit": "", - "name-zoom-mode": "", + "name-arc-sections": "Ívszakaszok", + "name-layout-algorithm": "Elrendezési algoritmus", + "name-main-stat-unit": "Fő statisztikai egység", + "name-secondary-stat-unit": "Másodlagos statisztikai egység", + "name-zoom-mode": "Nagyítási mód", "zoom-mode-options": { - "description-cooperative": "", - "description-greedy": "", - "label-cooperative": "", - "label-greedy": "" + "description-cooperative": "Lehetővé teszi az oldal normál görgetését", + "description-greedy": "Reagál minden nagyítási/kicsinyítési gesztusra", + "label-cooperative": "Együttműködéses", + "label-greedy": "Mohó" } }, "nodeGraph": { @@ -9827,40 +10343,40 @@ }, "registry": { "get-all-option-editors": { - "description-time-zone": "", - "name-time-zone": "" + "description-time-zone": "Időzóna kiválasztása", + "name-time-zone": "Időzóna" }, "standard-field-condigs": { - "category-data-links": "" + "category-data-links": "Adathivatkozások és műveletek" }, "standard-field-configs": { - "category": "", - "category-thresholds": "", - "category-value-mappings": "", - "description-display-name": "", - "description-field-min-max": "", - "description-max": "", - "description-min": "", - "description-no-value": "", - "description-value-mappings": "", - "name-actions": "", - "name-ad-hoc": "", - "name-color-scheme": "", - "name-data-links": "", - "name-decimals": "", - "name-display-name": "", - "name-field-min-max": "", - "name-max": "", - "name-min": "", - "name-no-value": "", - "name-thresholds": "", - "name-unit": "", - "name-value-mappings": "", - "placeholder-decimals": "", - "placeholder-display-name": "", - "placeholder-max": "", - "placeholder-min": "", - "placeholder-unit": "" + "category": "Standard opciók", + "category-thresholds": "Küszöbértékek", + "category-value-mappings": "Érték-hozzárendelések", + "description-display-name": "A mező vagy a sorozat nevének módosítása", + "description-field-min-max": "Min. és max. kiszámítása mezőnként", + "description-max": "Hagyja üresen az összes érték alapján történő számításhoz", + "description-min": "Hagyja üresen az összes érték alapján történő számításhoz", + "description-no-value": "Érték hiányában megjelenítendő", + "description-value-mappings": "Megjelenített szöveg módosítása a bemeneti érték alapján", + "name-actions": "Műveletek", + "name-ad-hoc": "Eseti alapon szűrhető", + "name-color-scheme": "Színséma", + "name-data-links": "Adathivatkozások", + "name-decimals": "Tizedesjegyek", + "name-display-name": "Megjelenített név", + "name-field-min-max": "Mező min./max. értéke", + "name-max": "Max.", + "name-min": "Min.", + "name-no-value": "Nincs érték", + "name-thresholds": "Küszöbértékek", + "name-unit": "Egység", + "name-value-mappings": "Érték-hozzárendelések", + "placeholder-decimals": "automatikus", + "placeholder-display-name": "nincs", + "placeholder-max": "automatikus", + "placeholder-min": "automatikus", + "placeholder-unit": "nincs" } }, "units": { @@ -9969,13 +10485,13 @@ }, "panel-data-error-view": { "missing-value": { - "number": "", - "string": "", - "time": "", - "unknown": "" + "number": "Az adatokból hiányzik egy számmező", + "string": "Az adatokból hiányzik egy karakterláncmező", + "time": "Az adatokból hiányzik egy időmező", + "unknown": "Nem lehet megjeleníteni az adatokat" }, "no-value": { - "default": "" + "default": "Nincs adat" }, "open-visualization-suggestions": "Vizualizációs javaslatok megnyitása", "switch-to-table": "Váltás táblázatra" @@ -10015,25 +10531,25 @@ "select-placeholder": "Szűrés típus szerint" }, "piechart": { - "category-legend": "", - "category-pie-chart": "", - "description-labels": "", - "description-pie-chart-type": "", + "category-legend": "Jelmagyarázat", + "category-pie-chart": "Kördiagram", + "description-labels": "Válassza ki a kördiagramban megjelenítendő címkéket", + "description-pie-chart-type": "Kördiagram megjelenítési módja", "labels-options": { - "label-name": "", - "label-percent": "", - "label-value": "" + "label-name": "Név", + "label-percent": "Százalék", + "label-value": "Érték" }, "legend-values-options": { - "label-percent": "", - "label-value": "" + "label-percent": "Százalék", + "label-value": "Érték" }, - "name-labels": "", - "name-legend-values": "", - "name-pie-chart-type": "", + "name-labels": "Címkék", + "name-legend-values": "Jelmagyarázati értékek", + "name-pie-chart-type": "Kördiagram típusa", "pie-chart-type-options": { - "label-donut": "", - "label-pie": "" + "label-donut": "Fánkdiagram", + "label-pie": "Kördiagram" } }, "playlist": { @@ -10201,7 +10717,7 @@ "dependencies": "Függőségek", "documentation": "Dokumentáció", "downloads": "Letöltések", - "from": "", + "from": "Kezdet", "installedVersion": "Telepített verzió", "lastCommitDate": "Legutóbbi teljesítés dátuma:", "latestReleaseDate": "Legújabb kiadás dátuma:", @@ -10271,7 +10787,7 @@ }, "install-controls-warning": { "body-not-published": "Ez a bővítmény nincs közzétéve a <2>grafana.com/plugins weboldalon, és nem kezelhető a katalóguson keresztül.", - "enterprise-plugin-info": "", + "enterprise-plugin-info": "Ez a beépülő modul csak a Grafana Cloud és a Grafana Enterprise szolgáltatásban érhető el.", "learn-more": "További információk", "no-valid-grafana-enterprise-license-detected": "Nem található érvényes Grafana Enterprise-licenc.", "title-dev-alert": "Ez a bővítmény fejlesztési változata, és nem távolítható el.", @@ -10429,28 +10945,50 @@ }, "provisioned-resource-form": { "save-or-delete-resource-shared-fields": { - "comment-placeholder-describe-changes-optional": "", - "description-branch-name-in-git-hub": "", + "comment-placeholder-describe-changes-optional": "Megjegyzés hozzáadása a módosítások leírásához (nem kötelező)", + "description-branch-name-in-git-hub": "Ág neve a GitHubon", "description-inside-repository": "", - "label-branch": "", - "label-comment": "", - "label-path": "", - "label-workflow": "" + "label-branch": "Ág", + "label-comment": "Megjegyzés", + "label-path": "Útvonal", + "label-workflow": "Munkafolyamat" } }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Ez a funkció jelenleg aktív fejlesztés alatt áll. A legjobb élmény és a legújabb fejlesztések érdekében javasoljuk a Grafana <2>éjszakai buildjének használatát." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Adjon egy egyértelmű nevet ehhez az adattári kapcsolathoz", "empty": "Üres", @@ -10460,8 +10998,12 @@ "label-display-name": "Megjelenített név", "placeholder-my-repository-connection": "Saját adattári kapcsolat", "text-loading-resource-information": "Erőforrás-információk betöltése…", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} fájl", - "files-count_other": "{{count}} fájl" + "files-count_other": "{{count}} fájl", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Ellenőrzés" @@ -10472,36 +11014,20 @@ "button-save": "Mentés", "button-saving": "Mentés...", "description-enabled": "Az automatikus lekérés engedélyezése után a cél nem módosítható.", - "description-path": "Egy alkönyvtár elérési útja a Git-tárban", "description-read-only": "Az erőforrások nem módosíthatók a Grafanán keresztül.", - "description-repository-url": "Adja meg a GitHub-adattár URL-címét", "description-title": "A konfiguráció emberek által olvasható neve", "error-required": "A mező kitöltése kötelező.", "error-save-repository": "Az adattár beállításainak mentése sikertelen volt", - "error-valid-github-url": "Adjon meg egy érvényes GitHub-adattár-URL-címet", "label-automatic-pulling": "Automatikus lekérés", - "label-branch": "Ág", "label-enabled": "Engedélyezve", - "label-github-token": "GitHub-token", "label-interval-seconds": "Intervallum (másodperc)", - "label-local-path": "Helyi elérési út", - "label-path": "Útvonal", - "label-pr-workflow": "Lekérési kérelemopció engedélyezése mentéskor", "label-repository-type": "Adattár típusa", - "label-repository-url": "Adattár URL-címe", "label-target": "Cél", "label-title": "Cím", "option-entire-instance": "Teljes példány", - "option-github": "GitHub", - "option-local": "Helyi", "option-managed-folder": "Felügyelt mappa", - "placeholder-branch": "fő", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/adattár/elérési/útja", - "placeholder-my-config": "Saját konfiguráció", - "placeholder-select-repository-type": "Adattár típusának kijelölése" + "placeholder-my-config": "Saját konfiguráció" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Konfigurálja a webkapcsokat, hogy azonnali frissítéseket kapjon a Grafanában, amint a változások végrehajtása megtörténik. Tekintse át és hagyja jóvá a módosításokat a lekérések használatával, mielőtt életbe lépnének.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Konfigurálás", - "configure-file": "Fájlkiépítés konfigurálása", - "configure-git-sync": "Git-szinkronizálás konfigurálása", "repository-limit-info-alert": "Elérte az adattár korlátját ({{count}})" }, - "connect-step": { - "description-branch": "A GitHub-adattárhoz használandó ág", - "description-github-path": "Ez annak az alkönyvtárnak az elérési útja az Ön GitHub-adattárában, amely az irányítópultok tárolására és kiépítésére szolgál", - "description-paste-your-git-hub-personal-access-token": "Illessze be a személyes GitHub-hozzáférési tokent", - "description-repository-url": "Illessze be a GitHub-adattár URL-címét", - "error-field-required": "A mező kitöltése kötelező.", - "error-invalid-github-url": "Adjon meg egy érvényes GitHub-adattár-URL-címet", - "label-access-token": "Hozzáférési GitHub-token", - "label-branch": "Ág neve", - "label-local-path": "Helyi elérési út", - "label-path": "Az alkönyvtár elérési útja az adattárban", - "label-repository-url": "GitHub-adattár URL-címe", - "placeholder-branch": "fő", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/adattár/elérési/útja" - }, "delete-repository-button": { "button-delete": "Törlés", "confirm-delete-repository": "Biztosan törli az adattár konfigurációját?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Azonnali frissítések és lekérések webkapcsokkal.", "title-visual-previews-in-pull-requests": "Vizuális előnézetek a lekérésekben képalkotással" }, + "error-title-default": "", "expanded-row": { "job-specification": "Feladat részletei", - "summary": "Összegzés", - "title-error": "Hiba" + "summary": "Összegzés" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Kötelező funkcióváltások beállítása" }, - "manage-dashboards-provision-updates-automatically": "Irányítópultok kezelése kódként a GitHubon, és a kiépítés automatikus frissítése", - "manage-your-dashboards-with-remote-provisioning": "Használat megkezdése a GitSynckel", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Tárolja az irányítópultokat verziókövetéses tárolóban a jobb rendszerezés és az előzmények nyomon követése érdekében" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Hozzáadja az irányítópult változásainak előnézetét a lekérésekben. A Grafana-irányítópultok képei meg lesznek osztva a Git-tárban, és bárki számára láthatóak lesznek, aki hozzáféréssel rendelkezik az adattárhoz.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Képalkotást igényel. <2>Állítsa be a képalkotást", - "description-often-shall-instance-updates-git-hub": "Milyen gyakran kérjen le frissítéseket a példány a GitHubról?", - "description-pr-enable-description": "Lehetővé teszi a felhasználók számára, hogy kiválasszák, megnyitják-e a lekérési kérelmet a módosítások mentésekor. Ha az adattár nem teszi lehetővé a főág közvetlen módosítását, akkor is szükség lehet lekérési kérelemre.", + "description-preview-requirements": "", "description-read-only": "Az erőforrások nem módosíthatók a Grafanán keresztül.", - "description-webhooks-enable": "Lehetővé teszi a felhasználók számára, hogy kiválasszák, megnyitják-e a lekérési kérelmet a módosítások mentésekor. Ha az adattár nem teszi lehetővé a főág közvetlen módosítását, akkor is szükség lehet lekérési kérelemre.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Az irányítópult előnézeteinek engedélyezése a lekérésekben", - "label-pr-workflow": "Lekérési kérelemopció engedélyezése mentéskor", + "label-generate-dashboard-previews": "", "label-read-only": "Csak olvasható", - "label-update-instance-interval-seconds": "Példányfrissítési időköz (másodpercben)", - "placeholder": "60", - "text-setup-later": "Ez később bármikor beállítható", - "title-enhance-github": "Fokozza a GitHub-élményét" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Nincs találat a lekérdezésre", @@ -10640,6 +11145,57 @@ "header": "Kiépítés", "subtitle-provisioning-feature": "Kiépítési kapcsolatok megtekintése és kezelése" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Nem található" }, @@ -10666,9 +11222,17 @@ "starting": "Kezdés…", "status": { "title-error-running-job": "Hiba történt a feladat futtatásakor", - "title-job-completed-successfully": "A feladat sikeresen befejeződött" + "title-warning-running-job": "" }, - "summary": "Összegzés" + "summary": "Összegzés", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Beállítások", - "source-code": "Forráskód" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Örökölt tárolás", "title-queued-for-deletion": "Törlésre vár" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Alap", "dashboard-preview": "Irányítópult előnézete", @@ -10788,6 +11370,11 @@ "label-current-step": "Jelenlegi lépés", "label-pending-step": "Függőben lévő lépés" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "A konfiguráció szerkesztése", "button-edit": "Szerkesztés", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Nem lehet beolvasni egy nem megfelelő állapotú adattárat" }, "synchronize-step": { - "error-no-job-id": "A feladat indítása nem sikerült", - "error-no-repository-name": "Nincs megadva az adattár neve", - "error-starting-job": "Hiba a feladat indításakor", "synchronization-description": "Változtatások belefoglalása minden előzményértékhez", "synchronization-options": "Szinkronizálási beállítások" }, @@ -10809,6 +11393,7 @@ "go-to": "Tovább ide:", "make-sure": "Mindenképpen foglalja bele ezeket az engedélyeket" }, + "warning-title-default": "", "wizard": { "alert-point-1": "A folyamat során nem lehet erőforrásokat létrehozni, szerkeszteni vagy törölni. Az utolsó lépésben el fognak tűnni.", "alert-point-2": "A kiépítés befejezése után az erőforrások újra megjelennek, és külső tárolóeszközön keresztül kezelhetők.", @@ -11715,112 +12300,112 @@ }, "stat": { "add-orientation-option": { - "description-orientation": "", - "name-orientation": "", + "description-orientation": "Elrendezés tájolása", + "name-orientation": "Tájolás", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-vertical": "" + "label-auto": "Automatikus", + "label-horizontal": "Vízszintes", + "label-vertical": "Függőleges" } }, "add-standard-data-reduce-options": { - "category-value-options": "", - "description-calculation": "", - "description-fields": "", - "description-limit": "", - "description-show": "", + "category-value-options": "Értékbeállítások", + "description-calculation": "Válasszon egy szűkítőfüggvényt/-számítást", + "description-fields": "Válassza ki a panelen szerepeltetni kívánt mezőket", + "description-limit": "Megjelenítendő sorok maximális száma", + "description-show": "Oszloponként vagy sorozatként egyetlen érték kiszámítása vagy az egyes sorok megjelenítése", "fields-options": { - "label-all-fields": "", - "label-numeric-fields": "" + "label-all-fields": "Minden mező", + "label-numeric-fields": "Numerikus mezők" }, - "name-calculation": "", - "name-fields": "", - "name-limit": "", - "name-show": "", + "name-calculation": "Számítás", + "name-fields": "Mezők", + "name-limit": "Korlát", + "name-show": "Megjelenítés", "show-options": { - "label-all-values": "", - "label-calculate": "" + "label-all-values": "Összes érték", + "label-calculate": "Számítás" } }, - "category-stat-styles": "", + "category-stat-styles": "Statisztikai stílusok", "color-mode-options": { - "label-background-gradient": "", - "label-background-solid": "", - "label-none": "", - "label-value": "" + "label-background-gradient": "Színátmenetes háttér", + "label-background-solid": "Egyszínű háttér", + "label-none": "Nincs", + "label-value": "Érték" }, - "description-graph-mode": "", - "description-text-mode": "", + "description-graph-mode": "Statisztikai panel grafikonos/értékgörbés üzemmódja", + "description-text-mode": "Szabályozza, hogy a név és az érték is megjelenjen, vagy csak a név", "graph-mode": { "options": { - "label-area": "", - "label-none": "" + "label-area": "Terület", + "label-none": "Nincs" } }, "name-color-modcolor-mode-options": { - "label": "" + "label": "Színezett mód" }, - "name-graph-mode": "", - "name-show-percent-change": "", - "name-text-alignment": "", - "name-text-mode": "", - "name-wide-layout": "", - "percent-change-color-mode": "", + "name-graph-mode": "Grafikonos mód", + "name-show-percent-change": "Változások százalékos arányának megjelenítése", + "name-text-alignment": "Szöveg igazítása", + "name-text-mode": "Szöveges mód", + "name-wide-layout": "Széles elrendezés", + "percent-change-color-mode": "Százalékos változás színezett módja", "percent-change-color-mode-options": { - "label-inverted": "", - "label-same-as-value": "", - "label-standard": "" + "label-inverted": "Invertált", + "label-same-as-value": "Értékkel megegyező", + "label-standard": "Standard" }, "text-alignment-options": { - "label-auto": "", - "label-center": "" + "label-auto": "Automatikus", + "label-center": "Középre" }, "text-mode-options": { - "label-auto": "", - "label-name": "", - "label-none": "", - "label-value": "", - "label-value-and-name": "" + "label-auto": "Automatikus", + "label-name": "Név", + "label-none": "Nincs", + "label-value": "Érték", + "label-value-and-name": "Érték és név" }, "wide-layout-options": { - "label-off": "", - "label-on": "" + "label-off": "Ki", + "label-on": "Be" } }, "state-timeline": { "align-values-options": { - "label-center": "", - "label-left": "", - "label-right": "" + "label-center": "Középre", + "label-left": "Balra", + "label-right": "Jobbra" }, - "category-state-timeline": "", - "name-align-values": "", - "name-connect-null-values": "", - "name-disconnect-values": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-merge-equal-consecutive-values": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-state-timeline": "Állapot idővonala", + "name-align-values": "Értékek igazítása", + "name-connect-null-values": "Nulla értékek összekapcsolása", + "name-disconnect-values": "Értékek leválasztása", + "name-fill-opacity": "Kitöltési átlátszóság", + "name-line-width": "Vonal szélessége", + "name-merge-equal-consecutive-values": "Egyenlő, egymást követő értékek egyesítése", + "name-page-size": "Oldalméret (oldalszámozás engedélyezése)", + "name-row-height": "Sormagasság", + "name-show-values": "Értékek megjelenítése", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Mindig", + "label-auto": "Automatikus", + "label-never": "Soha" } }, "status-history": { - "category-status-history": "", - "name-column-width": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-status-history": "Állapotelőzmények", + "name-column-width": "Oszlopszélesség", + "name-fill-opacity": "Kitöltési átlátszóság", + "name-line-width": "Vonal szélessége", + "name-page-size": "Oldalméret (oldalszámozás engedélyezése)", + "name-row-height": "Sormagasság", + "name-show-values": "Értékek megjelenítése", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Mindig", + "label-auto": "Automatikus", + "label-never": "Soha" }, "status-history-panel": { "too-many-points_one": "Túl sok a pont a megfelelő megjelenítéshez. <1>Frissítse a lekérdezést, hogy kevesebb pontot adjon vissza. <3>({{count}} pont érkezett)", @@ -11860,13 +12445,13 @@ "label-gauge-display-mode": "Mérőeszköz kijelzési módja", "label-value-display": "Érték megjelenítése" }, - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Cellabeállítások", + "category-table": "Táblázat", + "category-table-footer": "Táblázat lábléce", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Nagy", + "label-medium": "Közepes", + "label-small": "Kicsi" }, "color-background-cell-options-editor": { "description-apply-to-entire-row": "Ha ki van választva, a teljes sor olyan színű lesz, mint ez a cella.", @@ -11876,26 +12461,25 @@ }, "label-apply-to-entire-row": "Alkalmazás a teljes sorra", "label-background-display-mode": "Háttér megjelenítési módja", - "label-wrap-text": "Sortörés", "wrap-text": "Sortörés" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Automatikus", + "label-center": "Középre", + "label-left": "Balra", + "label-right": "Jobbra" }, "container": { "content": "Ha túl sok oszlopot jelenít meg egyetlen táblázatban, az hatással lehet a teljesítményre, és megnehezítheti az adatok olvasását. Mérlegelje a lekérdezések pontosítását.", "show-all-series": "Összes oszlop megjelenítése", "show-only-series": "Csak {{MAX_NUMBER_OF_COLUMNS}} oszlop megjelenítése" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", + "description-calculation": "Válasszon egy szűkítőfüggvényt/-számítást", + "description-cell-value-inspect": "A cellaérték ellenőrzésének engedélyezése egy modális ablakban", + "description-column-filter": "Engedélyezi/letiltja a mezőszűrőket a táblázatban", + "description-count-rows": "Egyetlen szám megjelenítése az összes adatsorhoz", + "description-fields": "Válassza ki a kiszámítandó mezőket", + "description-min-column-width": "Az oszlop automatikus átméretezésének minimális szélessége", "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Helyettesítő szöveg, amely akkor jelenik meg, ha egy kép nem jeleníthető meg, és amely a képernyőolvasót használó felhasználóknak hasznos", @@ -11903,61 +12487,61 @@ "label-alt-text": "Helyettesítő szöveg", "label-title-text": "Cím szövege" }, - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-paginations": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "name-wrap-header-text": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "name-calculation": "Számítás", + "name-cell-height": "Cellamagasság", + "name-cell-type": "Cellatípus", + "name-cell-value-inspect": "Cellaérték ellenőrzése", + "name-column-alignment": "Oszlop igazítása", + "name-column-filter": "Oszlopszűrő", + "name-column-width": "Oszlopszélesség", + "name-count-rows": "Sorok számlálása", + "name-enable-paginations": "Oldalszámozás engedélyezése", + "name-fields": "Mezők", + "name-hide-in-table": "Elrejtés a táblázatban", + "name-min-column-width": "Minimális oszlopszélesség", + "name-show-table-footer": "Táblázatlábléc megjelenítése", + "name-show-table-header": "Táblázatfejléc megjelenítése", + "name-wrap-header-text": "Fejléc szövegének tördelése", + "placeholder-column-width": "automatikus", + "placeholder-fields": "Numerikus mezők" }, "table-new": { - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Cellabeállítások", + "category-table": "Táblázat", + "category-table-footer": "Táblázat lábléce", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Nagy", + "label-medium": "Közepes", + "label-small": "Kicsi" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Automatikus", + "label-center": "Középre", + "label-left": "Balra", + "label-right": "Jobbra" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-pagination": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "description-calculation": "Válasszon egy szűkítőfüggvényt/-számítást", + "description-cell-value-inspect": "A cellaérték ellenőrzésének engedélyezése egy modális ablakban", + "description-column-filter": "Engedélyezi/letiltja a mezőszűrőket a táblázatban", + "description-count-rows": "Egyetlen szám megjelenítése az összes adatsorhoz", + "description-fields": "Válassza ki a kiszámítandó mezőket", + "description-min-column-width": "Az oszlop automatikus átméretezésének minimális szélessége", + "name-calculation": "Számítás", + "name-cell-height": "Cellamagasság", + "name-cell-type": "Cellatípus", + "name-cell-value-inspect": "Cellaérték ellenőrzése", + "name-column-alignment": "Oszlop igazítása", + "name-column-filter": "Oszlopszűrő", + "name-column-width": "Oszlopszélesség", + "name-count-rows": "Sorok számlálása", + "name-enable-pagination": "Oldalszámozás engedélyezése", + "name-fields": "Mezők", + "name-hide-in-table": "Elrejtés a táblázatban", + "name-min-column-width": "Minimális oszlopszélesség", + "name-show-table-footer": "Táblázatlábléc megjelenítése", + "name-show-table-header": "Táblázatfejléc megjelenítése", + "placeholder-column-width": "automatikus", + "placeholder-fields": "Numerikus mezők" }, "tag-filter": { "clear-button": "Címkék törlése", @@ -12042,17 +12626,17 @@ } }, "text": { - "category-text": "", + "category-text": "Szöveg", "mode-options": { - "label-code": "", - "label-html": "", - "label-markdown": "" + "label-code": "Kód", + "label-html": "HTML", + "label-markdown": "Markdown" }, - "name-content": "", - "name-language": "", - "name-mode": "", - "name-show-line-numbers": "", - "name-show-mini-map": "" + "name-content": "Tartalom", + "name-language": "Nyelv", + "name-mode": "Mód", + "name-show-line-numbers": "Sorszámozás megjelenítése", + "name-show-mini-map": "Minitérkép megjelenítése" }, "theme-preview": { "breadcrumbs": { @@ -12100,7 +12684,7 @@ "apply-button": "Időtartomány alkalmazása", "default-error": "Adjon meg egy múltbeli dátumot vagy a „{{now}}” értéket", "fiscal-year": "Pénzügyi év: {{from}} – {{to}}", - "from-input": "", + "from-input": "Kezdet", "open-input-calendar": "Naptár megnyitása", "range-error": "A „Kezdete” nem lehet a „Vége” után", "to-input": "Vége" @@ -12118,7 +12702,7 @@ "aria-role": "Időtartomány kijelölése", "default-title": "Időtartományok", "example-title": "Példa időtartományokra", - "from-label": "", + "from-label": "Kezdet", "from-to": "{{timeOptionFrom}} – {{timeOptionTo}}", "specify": "Adja meg az időtartományt", "submit-button-label": "TimePicker küldés gombja", @@ -12131,8 +12715,8 @@ }, "timeline": { "missing-field": { - "all": "", - "time": "" + "all": "Nincsenek grafikonon megjeleníthető mezők", + "time": "Az adatok nem rendelkeznek időmezővel" } }, "timeseries": { @@ -12150,54 +12734,54 @@ "tooltip-delete": "Törlés", "tooltip-edit": "Szerkesztés" }, - "category-axis": "", + "category-axis": "Tengely", "config": { "get-graph-field-config": { - "aria-label-bar-width-factor": "", - "aria-label-fill-opacity": "", - "aria-label-line-width": "", - "aria-label-point-size": "", - "category-graph-styles": "", - "category-thresholds": "", - "name-bar-alignment": "", - "name-bar-width-factor": "", - "name-connect-nulls": "", - "name-disconnect-values": "", - "name-fill-below-to": "", - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-interpolation": "", - "name-line-style": "", - "name-line-width": "", - "name-point-size": "", - "name-show-points": "", - "name-show-thresholds": "", - "name-style": "", - "name-transform": "", + "aria-label-bar-width-factor": "Sávszélességi tényező", + "aria-label-fill-opacity": "Kitöltési átlátszóság", + "aria-label-line-width": "Vonal szélessége", + "aria-label-point-size": "Pontméret", + "category-graph-styles": "Grafikonstílusok", + "category-thresholds": "Küszöbértékek", + "name-bar-alignment": "Sáv igazítása", + "name-bar-width-factor": "Sávszélességi tényező", + "name-connect-nulls": "Nulla értékek összekapcsolása", + "name-disconnect-values": "Értékek leválasztása", + "name-fill-below-to": "Kitöltés alatta eddig:", + "name-fill-opacity": "Kitöltési átlátszóság", + "name-gradient-mode": "Színátmenetes mód", + "name-line-interpolation": "Vonalinterpoláció", + "name-line-style": "Vonal stílusa", + "name-line-width": "Vonal szélessége", + "name-point-size": "Pontméret", + "name-show-points": "Pontok megjelenítése", + "name-show-thresholds": "Küszöbértékek megjelenítése", + "name-style": "Stílus", + "name-transform": "Transzformáció", "transform-options": { - "description-constant": "", - "description-style": "", - "label-constant": "", - "label-style": "" + "description-constant": "Az első érték állandó vonalként jelenik meg", + "description-style": "Az eredmények negatív értékekre fordítása az y tengelyen", + "label-constant": "Konstans", + "label-style": "Negatív Y" } } }, "insert-nulls-editor": { "disconnect-options": { - "label-never": "", - "label-threshold": "" + "label-never": "Soha", + "label-threshold": "Küszöb" } }, "line-style-editor": { "line-fill-options": { - "label-dash": "", - "label-dots": "", - "label-solid": "" + "label-dash": "Szaggatott", + "label-dots": "Pontozott", + "label-solid": "Folytonos" }, "title-the-input-expects-a-segment-list": "A bemenet szegmenslistát vár", "tooltip-help": "Súgó" }, - "name-time-zone": "", + "name-time-zone": "Időzóna", "nulls-threshold-input": { "placeholder-never": "Soha" }, @@ -12207,9 +12791,9 @@ }, "span-nulls-editor": { "gaps-options": { - "label-always": "", - "label-never": "", - "label-threshold": "" + "label-always": "Mindig", + "label-never": "Soha", + "label-threshold": "Küszöb" } }, "timezones-editor": { @@ -12218,17 +12802,17 @@ } }, "traces": { - "category-span-filters": "", - "name-critical-path-only": "", - "name-find-in-trace": "", - "name-max-duration": "", - "name-min-duration": "", - "name-service-name": "", - "name-service-name-operator": "", - "name-show-matches-only": "", - "name-span-name": "", - "name-span-name-operator": "", - "name-tags": "", + "category-span-filters": "Terjedelemszűrők", + "name-critical-path-only": "Csak a kritikus útvonal megjelenítése", + "name-find-in-trace": "Keresés a nyomvonalon", + "name-max-duration": "Max. időtartam", + "name-min-duration": "Min. időtartam", + "name-service-name": "Szolgáltatásnév", + "name-service-name-operator": "Szolgáltatásnév operátora", + "name-show-matches-only": "Csak egyezések megjelenítése", + "name-span-name": "Terjedelemnév", + "name-span-name-operator": "Terjedelemnév operátora", + "name-tags": "Címkék", "traces-panel": { "no-data-found-in-response": "Nem található adat a válaszban" } @@ -12243,20 +12827,20 @@ "axis-editor": { "log-mode-options": { "description": { - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-size": "Tárolók felosztása méret alapján" }, "label": { - "split": "" + "split": "Osztott" } }, "mode-options": { "description": { - "split-the-buckets-based-on-count": "", - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-count": "Tárolók felosztása szám alapján", + "split-the-buckets-based-on-size": "Tárolók felosztása méret alapján" }, "label": { - "count": "", - "size": "" + "count": "Szám", + "size": "Méret" } }, "placeholder-auto": "Automatikus" @@ -12288,9 +12872,9 @@ }, "calculate-heatmap": { "add-heatmap-calculation-options": { - "name-x-bucket": "", - "name-y-bucket": "", - "name-y-bucket-scale": "" + "name-x-bucket": "X tároló", + "name-y-bucket": "Y tároló", + "name-y-bucket-scale": "Y tároló skálája" } }, "concatenate-transformer-editor": { @@ -12351,8 +12935,8 @@ "tooltip-enable": "Engedélyezés" }, "draggable-sort-order": { - "asc": "", - "desc": "", + "asc": "NÖVEKVŐ", + "desc": "CSÖKKENŐ", "off": "Ki" }, "editors": { @@ -12751,9 +13335,9 @@ "description": { "reorder-hide-or-rename-fields": "" }, - "field-order": "", + "field-order": "Mezőrendezés", "field-order-auto": "Automatikus", - "field-order-manual": "", + "field-order-manual": "Kézi", "first-frame-warning": "A mezők rendszerezése csak egyetlen kerettel működik. Először érdemes lehet egyesítési transzformációt alkalmazni, vagy szűrni a bemenetet.", "name": { "organize-fields": "" @@ -12880,7 +13464,7 @@ "polynomial": "Polinomiális" } }, - "regression": "", + "regression": "regresszió", "tooltip-number-of-xy-points-to-predict": "Előre jelzendő X,Y pontok száma" }, "rename-by-regex-transformer": { @@ -13033,10 +13617,10 @@ } }, "trend": { - "category-x-axis": "", - "description-x-field": "", - "name-x-field": "", - "placeholder-x-field": "" + "category-x-axis": "X tengely", + "description-x-field": "Növekvő számérték", + "name-x-field": "X mező", + "placeholder-x-field": "Első számérték" }, "upgrade-box": { "discovery-text": "Felfedezett egy Pro funkciót!", @@ -13299,20 +13883,20 @@ } }, "xychart": { - "category-xychart": "", - "name-fill-opacity": "", - "name-line-style": "", - "name-line-width": "", - "name-max-point-size": "", - "name-min-point-size": "", - "name-point-shape": "", - "name-point-size": "", - "name-point-stroke-width": "", - "name-series-mapping": "", - "name-show": "", + "category-xychart": "XY diagram", + "name-fill-opacity": "Kitöltési átlátszóság", + "name-line-style": "Vonal stílusa", + "name-line-width": "Vonal szélessége", + "name-max-point-size": "Max. pontméret", + "name-min-point-size": "Min. pontméret", + "name-point-shape": "Pontalak", + "name-point-size": "Pontméret", + "name-point-stroke-width": "Pont vonalvastagsága", + "name-series-mapping": "Sorozat-hozzárendelés", + "name-show": "Megjelenítés", "point-shape-options": { - "label-circle": "", - "label-square": "" + "label-circle": "Kör", + "label-square": "Szögletes" }, "series-editor": { "add-series": "Sorozat hozzáadása", @@ -13324,18 +13908,18 @@ "label-y-field": "Y mező", "placeholder-all-frames": "Összes keret", "placeholder-select-frame": "Keret kiválasztása", - "placeholder-x-field": "", - "placeholder-y-field": "", + "placeholder-x-field": "Első szám vagy időmező minden keretben", + "placeholder-y-field": "Fennmaradó számmezők minden keretben", "tooltip-delete-series": "Sorozat törlése" }, "series-mapping-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Automatikus", + "label-manual": "Kézi" }, "show-options": { - "label-both": "", - "label-lines": "", - "label-points": "" + "label-both": "Mindkettő", + "label-lines": "Vonalak", + "label-points": "Pontok" } } } \ No newline at end of file diff --git a/public/locales/i18next-parser.config.cjs b/public/locales/i18next-parser.config.cjs index 10f3bf00594..972a9cad866 100644 --- a/public/locales/i18next-parser.config.cjs +++ b/public/locales/i18next-parser.config.cjs @@ -14,6 +14,7 @@ module.exports = { '!../../public/app/extensions/**/*', // Don't extract from Enterprise '!../../public/app/plugins/datasource/**/*', // Don't extract from datasource plugins '../../packages/grafana-ui/**/*.{tsx,ts}', + '../../packages/grafana-data/**/*.{tsx,ts}', ], output: './public/locales/$LOCALE/$NAMESPACE.json', }; diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index b909873eeb7..1490c225d50 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -119,10 +119,10 @@ "users-heading": "Pengguna organisasi" }, "enterprise-auth-features-card": { - "dismiss": "", - "heading": "", - "learn-more-link": "", - "text": "" + "dismiss": "Tutup", + "heading": "Autentikasi perusahaan", + "learn-more-link": "Pelajari lebih lanjut", + "text": "Kelola pengguna, tim, dan izin secara otomatis dengan <1>SAML, <3>SCIM, <6>LDAP, dan <8>RBAC — tersedia di Grafana Cloud dan Enterprise." }, "feature-listing": { "title-auditing": "Audit", @@ -374,8 +374,8 @@ "am-active-timing-select-label-active-timings": "Pengaturan waktu aktif" }, "ad": { - "close": "", - "learn-more": "" + "close": "Tutup", + "learn-more": "Pelajari lebih lanjut" }, "add-button": { "add-more": "Tambah lagi" @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -404,12 +405,12 @@ "missing-series-resolve": "Rangkaian tidak ada, evaluasi gagal diselesaikan", "no-annotations": "Tidak ada anotasi", "notification-configuration": { - "active-timings": "", + "active-timings": "Interval waktu aktif", "contact-point": "Titik kontak", "group-by": "Dikelompokkan berdasarkan", - "group-title": "", + "group-title": "Konfigurasi pemberitahuan", "mute-timings": "Pengaturan waktu pembisuan", - "timing-options": "" + "timing-options": "Pengaturan waktu" }, "pending-period": "Periode tertunda", "rule": "Aturan", @@ -957,7 +958,7 @@ }, "contactPoints": { "validation": { - "notFound": "" + "notFound": "Titik kontak \"{{contactPoint}}\" tidak dapat ditemukan" } }, "continue-matching-indicator": { @@ -966,7 +967,7 @@ "copy-to-clipboard": "Salin \"{{label}}\" ke papan klip", "create-metadata": { "label": { - "contact-point": "", + "contact-point": "Pemberitahuan dikirimkan ke", "dashboard": "Dasbor", "dashboard-and-panel": "Dasbor dan panel", "evaluation-interval": "Interval evaluasi", @@ -1114,7 +1115,7 @@ "content-show-all-errors": "Tampilkan semua kesalahan" }, "errors": { - "failedWith": "" + "failedWith": "{{-config}} gagal dengan {{status}}: {{-message}}" }, "evaluate-every-validation-options": { "message": { @@ -1545,7 +1546,8 @@ "title": "Edit grup evaluasi" }, "group-loader": { - "group-load-failed": "Gagal memuat aturan dari grup {{ groupName }} di {{ namespaceName }}" + "group-load-failed": "Gagal memuat aturan dari grup {{ groupName }} di {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Grup sedang dihapus" @@ -1562,20 +1564,20 @@ } }, "home": { - "irm-card-description": "", - "irm-card-item-1": "", - "irm-card-item-2": "", - "irm-card-item-3": "", - "irm-card-item-4": "", - "irm-card-title": "", + "irm-card-description": "Gabungkan on-call, peringatan, dan respons insiden dengan Grafana Cloud IRM.", + "irm-card-item-1": "Kelola jadwal on-call dengan kalender Anda atau Terraform.", + "irm-card-item-2": "Tanggapi insiden melalui web, aplikasi, Slack, atau saluran lainnya.", + "irm-card-item-3": "Temukan akar masalah dengan Grafana SIFT yang didukung AI.", + "irm-card-item-4": "Lakukan analisis insiden terdahulu untuk meningkatkan respons dan ketahanan.", + "irm-card-title": "Respons dan manajemen insiden", "label-get-started": "Mulai", "label-insights": "Wawasan", - "synthetic-monitoring-card-description": "", - "synthetic-monitoring-card-item-1": "", - "synthetic-monitoring-card-item-2": "", - "synthetic-monitoring-card-item-3": "", - "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", + "synthetic-monitoring-card-description": "Pantau aliran pengguna, situs web, dan API yang krusial secara eksternal, dari lokasi global.", + "synthetic-monitoring-card-item-1": "Simulasikan perjalanan pengguna end-to-end dengan pemeriksaan browser.", + "synthetic-monitoring-card-item-2": "Jalankan pemeriksaan ping, DNS, HTTP/S, dan TCP di setiap layer jaringan.", + "synthetic-monitoring-card-item-3": "Gunakan 20+ probe global atau probe pribadi di belakang firewall Anda.", + "synthetic-monitoring-card-item-4": "Lacak SLO dengan peringatan yang mirip cara kerja Prometheus bawaan—langsung dari UI.", + "synthetic-monitoring-card-title": "Pemantauan Sintetis", "title-alerting": "Alerting" }, "import-to-gma": { @@ -1590,7 +1592,7 @@ "no-rules-body-yaml": "Tidak ada aturan untuk diimpor. Silakan pilih file yaml lain.", "no-rules-title": "Tidak ada aturan untuk diimpor", "not-using-rules-managed-by-integrations-or-plugins": { - "text": "", + "text": "Aturan yang dikelola oleh integrasi atau plugin tidak boleh diimpor ke aturan yang dikelola Grafana.", "title": "Informasi" }, "plugin-rules-warning": { @@ -1969,7 +1971,7 @@ }, "notification-policies-filter": { "label-search-by-contact-point": "Cari berdasarkan titik kontak", - "placeholder-search-by-contact-point": "", + "placeholder-search-by-contact-point": "Pilih titik kontak", "search-query-input-placeholder-search": "Cari" }, "notification-policies-list": { @@ -2456,7 +2458,6 @@ }, "rule-list": { "configure-datasource": "Konfigurasikan", - "draft-new-rule": "Buat draf aturan baru", "ds-error": { "title": "Tidak dapat memuat aturan untuk sumber data ini" }, @@ -2477,11 +2478,12 @@ "new-badge": "Baru!", "text": "Impor ke aturan yang dikelola Grafana" }, - "loading-more-groups": "", + "loading-more-groups": "Memuat lebih banyak grup...", "more": "Lebih banyak", "new-alert-rule": "Aturan peringatan baru", "new-datasource-recording-rule": "Aturan Perekaman sumber data baru", "new-grafana-recording-rule": "Aturan Perekaman Grafana baru", + "new-rule-for-export": "", "pagination": { "next-page": "Tampilkan lebih banyak..." }, @@ -2789,6 +2791,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Refresh pratinjau", + "empty-state": "", "label-preview": "Pratinjau", "refresh": "Muat ulang" }, @@ -3025,51 +3028,51 @@ } }, "alertlist": { - "category-alert-state-filter": "", - "category-filter": "", - "category-options": "", - "descriptino-alerts-linked-to-dashboard": "", - "description-alert-instance-label": "", - "description-alert-name": "", - "description-datasource": "", - "description-folder": "", - "description-group-by": "", - "description-group-mode": "", - "description-max-items": "", - "description-show-inactive-alerts": "", - "description-sort-order": "", - "description-view-mode": "", + "category-alert-state-filter": "Filter status peringatan", + "category-filter": "Filter", + "category-options": "Opsi", + "descriptino-alerts-linked-to-dashboard": "Hanya tampilkan peringatan yang ditautkan ke dasbor ini", + "description-alert-instance-label": "Filter instans peringatan menggunakan kueri label, misalnya: {severity=\"critical\", instance=~\"cluster-us-.+\"}", + "description-alert-name": "Filter untuk peringatan yang berisi teks ini", + "description-datasource": "Filter dari sumber peringatan", + "description-folder": "Filter untuk peringatan di folder yang dipilih (hanya untuk aturan peringatan yang dikelola Grafana)", + "description-group-by": "Filter peringatan menggunakan kueri label", + "description-group-mode": "Cara pengelompokan instans peringatan", + "description-max-items": "Peringatan maksimum untuk ditampilkan", + "description-show-inactive-alerts": "Sertakan aturan peringatan yang memiliki 0 (nol) instans. Karena tidak memiliki instans, aturan ini tetap tersembunyi jika filter label instans Peringatan dikonfigurasi.", + "description-sort-order": "Urutan peringatan dan instans peringatan", + "description-view-mode": "Beralih antara tampilan daftar dan tampilan statistik", "group-by": { "aria-label-group-by-label-keys": "kelompokkan berdasarkan kunci label", "placeholder-group-by": "Kelompokkan berdasarkan" }, "group-mode-options": { - "label-custom-grouping": "", - "label-default-grouping": "" + "label-custom-grouping": "Pengelompokan kustom", + "label-default-grouping": "Pengelompokan default" }, - "name-alert-instance-label": "", - "name-alert-name": "", - "name-alerts-linked-to-dashboard": "", - "name-datasource": "", - "name-error": "", - "name-firing": "", - "name-folder": "", - "name-group-by": "", - "name-group-mode": "", - "name-max-items": "", - "name-no-data": "", - "name-normal": "", - "name-pending": "", - "name-recovering": "", - "name-show-inactive-alerts": "", - "name-sort-order": "", - "name-view-mode": "", + "name-alert-instance-label": "Label instans peringatan", + "name-alert-name": "Nama peringatan", + "name-alerts-linked-to-dashboard": "Peringatan yang ditautkan ke dasbor ini", + "name-datasource": "Sumber data", + "name-error": "Kesalahan", + "name-firing": "Memberi/Memicu Peringatan", + "name-folder": "Folder", + "name-group-by": "Kelompokkan berdasarkan", + "name-group-mode": "Mode grup", + "name-max-items": "Item maks", + "name-no-data": "Tidak ada Data", + "name-normal": "Normal", + "name-pending": "Tertunda", + "name-recovering": "Memulihkan", + "name-show-inactive-alerts": "Tampilkan peringatan dengan 0 instans", + "name-sort-order": "Urutan penyortiran", + "name-view-mode": "Mode tampilan", "sort-order-options": { - "label-alphabetical-asc": "", - "label-alphabetical-desc": "", - "label-importance": "", - "label-time-asc": "", - "label-time-desc": "" + "label-alphabetical-asc": "Sesuai abjad (naik)", + "label-alphabetical-desc": "Sesuai abjad (turun)", + "label-importance": "Tingkat kepentingan", + "label-time-asc": "Waktu (naik)", + "label-time-desc": "Waktu (turun)" }, "ungrouped-mode-view": { "active-for": "selama <1>{{duration}}", @@ -3085,8 +3088,8 @@ "title-permission-required": "Izin diperlukan" }, "view-mode-options": { - "label-list": "", - "label-stat": "" + "label-list": "Daftar", + "label-stat": "Stat" } }, "annolist": { @@ -3099,31 +3102,31 @@ "annotation-list-item": { "tooltip-created-by": "Dibuat oleh:<1> {{email}}" }, - "category-annotation-query": "", - "category-display": "", - "category-link-behaviour": "", - "description-tags": "", + "category-annotation-query": "Kueri anotasi", + "category-display": "Tampilan", + "category-link-behaviour": "Perilaku tautan", + "description-tags": "Cocokkan tag anotasi", "link-target-options": { - "label-dashboard": "", - "label-panel": "" + "label-dashboard": "Dasbor", + "label-panel": "Panel" }, - "name-limit": "", - "name-link-target": "", - "name-query-filter": "", - "name-show-tags": "", - "name-show-time": "", - "name-show-user": "", - "name-tags": "", - "name-time-after": "", - "name-time-before": "", - "name-time-range": "", + "name-limit": "Batas", + "name-link-target": "Target tautan", + "name-query-filter": "Filter kueri", + "name-show-tags": "Tampilkan tag", + "name-show-time": "Tampilkan Waktu", + "name-show-user": "Tampilkan pengguna", + "name-tags": "Tag", + "name-time-after": "Waktu setelah", + "name-time-before": "Waktu sebelum", + "name-time-range": "Rentang waktu", "query-filter-options": { - "label-all-dashboards": "", - "label-this-dashboard": "" + "label-all-dashboards": "Semua dasbor", + "label-this-dashboard": "Dasbor ini" }, "time-range-options": { - "label-none": "", - "label-this-dashboard": "" + "label-none": "Tidak ada", + "label-this-dashboard": "Dasbor ini" } }, "annotations": { @@ -3243,6 +3246,11 @@ "id-token-attribute-name-label": "Nama atribut token ID", "login-attribute-path-description": "Ekspresi JMESPath yang digunakan untuk pencarian login pengguna dari token ID pengguna.", "login-attribute-path-label": "Jalur atribut login", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "ID klien identitas terkelola dari kredensial identitas federasi pada aplikasi OAuth2 Anda.", "managed-identity-client-id-label": "ID klien identitas yang dikelola FIC", "name-attribute-path-description": "Ekspresi JMESPath yang digunakan untuk pencarian nama pengguna dari token ID pengguna.\nNama ini akan digunakan sebagai nama tampilan pengguna.", @@ -3313,8 +3321,8 @@ } }, "provider-card": { - "saml-learn-more": "", - "scim-learn-more": "", + "saml-learn-more": "Sistem Masuk Tunggal (SSO) dengan SAML.", + "scim-learn-more": " Sinkronkan pengguna dan tim dengan SCIM.", "text-badge-enabled": "Aktif", "text-badge-not-enabled": "Tidak diaktifkan" }, @@ -3341,6 +3349,9 @@ "text-badge-enabled": "Aktif", "text-badge-not-enabled": "Tidak diaktifkan" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Endpoint .well known/openid-configuration untuk IdP Anda", "title-open-id-connect-discovery-url": "URL OpenID Connect Discovery" @@ -3351,99 +3362,99 @@ }, "bar-chart": { "warn": { - "missing-numeric": "", - "missing-series": "" + "missing-numeric": "Tidak ada bidang numerik yang ditemukan", + "missing-series": "Diagram batang memerlukan bidang string atau waktu" } }, "barchart": { "config": { - "category-thresholds": "", - "description-color-by-field": "", - "description-x-label-max-length": "", - "name-bar-radius": "", - "name-bar-width": "", - "name-color-by-field": "", - "name-fill-opacity": "", - "name-full-highlight": "", - "name-gradient-mode": "", - "name-group-width": "", - "name-line-width": "", - "name-orientation": "", - "name-rotate-x-labels": "", - "name-show-thresholds": "", - "name-show-values": "", - "name-stacking": "", - "name-transform": "", - "name-x-axis": "", - "name-x-label-max-length": "", - "name-x-label-min-spacing": "", + "category-thresholds": "Ambang batas", + "description-color-by-field": "Gunakan nilai warna untuk bidang berhirarki sama untuk mewarnai setiap nilai batang.", + "description-x-label-max-length": "Label sumbu X akan dipotong sesuai panjang yang ditentukan", + "name-bar-radius": "Radius batang", + "name-bar-width": "Lebar batang", + "name-color-by-field": "Warna berdasarkan bidang", + "name-fill-opacity": "Opasitas isian", + "name-full-highlight": "Sorot seluruh area saat mengarahkan kursor", + "name-gradient-mode": "Mode gradien", + "name-group-width": "Lebar grup", + "name-line-width": "Lebar garis", + "name-orientation": "Orientasi", + "name-rotate-x-labels": "Putar label centang sumbu x", + "name-show-thresholds": "Tampilkan ambang batas", + "name-show-values": "Tampilkan nilai", + "name-stacking": "Tumpukan", + "name-transform": "Ubah", + "name-x-axis": "Sumbu X", + "name-x-label-max-length": "Panjang maksimum label centang sumbu X", + "name-x-label-min-spacing": "Spasi minimum label sumbu X", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-line-vertical": "" + "label-auto": "Otomatis", + "label-horizontal": "Horizontal", + "label-line-vertical": "Vertikal" }, - "placeholder-x-axis": "", - "placeholder-x-label-max-length": "", + "placeholder-x-axis": "Bidang string atau waktu pertama", + "placeholder-x-label-max-length": "Tidak ada", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Selalu", + "label-auto": "Otomatis", + "label-never": "Jangan pernah" }, "transform-options": { - "description-constant": "", - "description-negative-y": "", - "label-constant": "", - "label-negative-y": "" + "description-constant": "Nilai pertama akan ditampilkan sebagai garis konstan", + "description-negative-y": "Balik hasil ke nilai negatif pada sumbu y", + "label-constant": "Konstan", + "label-negative-y": "Y Negatif" } }, "tick-spacing-editor": { "content-require-space-from-the-right-side": "Membutuhkan ruang dari sisi kanan", "gaps-options": { - "description-large": "", - "description-medium": "", - "description-none": "", - "description-small": "", - "label-large": "", - "label-medium": "", - "label-none": "", - "label-small": "" + "description-large": "Memerlukan spasi {{spacing}}", + "description-medium": "Memerlukan spasi {{spacing}}", + "description-none": "Tampilkan semua tanda centang", + "description-small": "Memerlukan spasi {{spacing}}", + "label-large": "Besar", + "label-medium": "Sedang", + "label-none": "Tidak ada", + "label-small": "Kecil" }, "label-rtl": "RTL" } }, "bargauge": { "bar-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Otomatis", + "label-manual": "Manual" }, - "category-bar-gauge": "", - "description-max-height": "", - "description-min-height": "", - "description-min-width": "", - "description-show-unfilled-area": "", + "category-bar-gauge": "Pengukur batang", + "description-max-height": "Tinggi baris maksimum (orientasi horizontal)", + "description-min-height": "Tinggi baris minimum (orientasi horizontal)", + "description-min-width": "Lebar kolom minimum (orientasi vertikal)", + "description-show-unfilled-area": "Saat diaktifkan, tampilkan area yang tidak terisi menjadi abu-abu", "display-mode-options": { - "label-basic": "", - "label-gradient": "", - "label-retro": "" + "label-basic": "Dasar", + "label-gradient": "Gradien", + "label-retro": "LCD Retro" }, - "name-bar-size": "", - "name-display-mode": "", - "name-max-height": "", - "name-min-height": "", - "name-min-width": "", - "name-name-placement": "", + "name-bar-size": "Ukuran batang", + "name-display-mode": "Mode tampilan", + "name-max-height": "Tinggi maks", + "name-min-height": "Tinggi min", + "name-min-width": "Lebar minimum", + "name-name-placement": "Penempatan nama", "name-placement-options": { - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-top": "" + "label-auto": "Otomatis", + "label-hidden": "Tersembunyi", + "label-left": "Kiri", + "label-top": "Atas" }, - "name-show-unfilled-area": "", - "name-value-display": "", + "name-show-unfilled-area": "Tampilkan area yang tidak terisi", + "name-value-display": "Tampilan nilai", "value-display-options": { - "label-hidden": "", - "label-text-color": "", - "label-value-color": "" + "label-hidden": "Tersembunyi", + "label-text-color": "Warna teks", + "label-value-color": "Warna nilai" } }, "bookmarks-page": { @@ -3458,7 +3469,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Batalkan", "cannot-move-folders": "Folder tidak dapat dipindahkan", "confirmation-text": "Hapus", @@ -3468,7 +3478,7 @@ "delete-modal-restore-dashboards-text": "Tindakan ini akan segera menghapus folder yang dipilih tetapi dasbor yang dipilih akan ditandai untuk dihapus dalam 30 hari. Administrator organisasi Anda dapat memulihkan dasbor kapan saja sebelum 30 hari kedaluwarsa. Folder tidak dapat dipulihkan.", "delete-modal-text": "Tindakan ini akan menghapus konten berikut:", "delete-modal-title": "Hapus", - "delete-provisioned-folder": "", + "delete-provisioned-folder": "Hapus folder yang disediakan", "deleting": "Menghapus...", "manage-permissions-button": "Kelola izin", "move-button": "Pindahkan", @@ -3491,6 +3501,23 @@ "browse-view": { "this-folder-is-empty": "Folder ini kosong" }, + "bulk-action-resources-form": { + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_other": "{{count}} aturan peringatan", "dashboard_other": "{{count}} dasbor", @@ -3507,12 +3534,12 @@ "tags-column": "Tag" }, "delete-provisioned-folder-form": { - "alert-folder-deleted-successfully": "", - "api-error": "", - "button-cancel": "", - "button-delete": "", - "button-deleting": "", - "delete-warning": "" + "alert-folder-deleted-successfully": "Folder berhasil dihapus", + "api-error": "Gagal menghapus folder", + "button-cancel": "Batalkan", + "button-delete": "Hapus", + "button-deleting": "Menghapus...", + "delete-warning": "Ini akan menghapus folder ini dan semua subfoldernya. Secara keseluruhan, ini akan memengaruhi:" }, "descendant-count": { "title-unable-to-retrieve-descendant-information": "Tidak dapat mengambil informasi turunan" @@ -3536,6 +3563,7 @@ "empty-message": "Folder tidak ditemukan", "error-title": "Kesalahan saat memuat folder", "non-folder-item": "Non-folder {{itemKind}} {{itemUID}} ", + "root-title": "", "search-placeholder": "Cari folder", "unknown-error": "Kesalahan tidak diketahui" }, @@ -3571,52 +3599,52 @@ "text": "Hasil untuk kueri Anda tidak ditemukan" }, "restore": { - "success": "" + "success": "Dasbor {{name}} dipulihkan" }, "text-this-repository-is-read-only": "Jika Anda memiliki akses langsung ke target, salin JSON dan tempel di sana.", "trash-state-manager": { "label": { - "alphabetically-az": "", - "alphabetically-za": "" + "alphabetically-az": "Sesuai abjad (A-Z)", + "alphabetically-za": "Sesuai abjad (Z-A)" } } }, "candlestick": { "additional-fields-options": { - "label-ignore": "", - "label-include": "" + "label-ignore": "Abaikan", + "label-include": "Meliputi" }, "candle-style-options": { - "label-candles": "", - "label-ohlc-bars": "" + "label-candles": "Lilin", + "label-ohlc-bars": "Batang OHLC" }, - "category-candlestick": "", + "category-candlestick": "Stik Lilin", "color-strategy-options": { - "label-since-open": "", - "label-since-prior-close": "" + "label-since-open": "Sejak Dibuka", + "label-since-prior-close": "Sejak Penutupan Sebelumnya" }, - "description-additional-fields": "", - "description-close": "", - "description-high": "", - "description-low": "", - "description-open": "", - "description-volume": "", + "description-additional-fields": "Gunakan opsi deret waktu standar untuk mengonfigurasi bidang apa pun yang tidak dipetakan di atas", + "description-close": "Nilai pada akhir periode", + "description-high": "Nilai maksimum dalam periode", + "description-low": "Nilai minimum dalam periode", + "description-open": "Nilai pada awal periode", + "description-volume": "Jumlah sampel dalam periode", "mode-options": { - "label-both": "", - "label-candles": "", - "label-volume": "" + "label-both": "Keduanya", + "label-candles": "Lilin", + "label-volume": "Volume" }, - "name-additional-fields": "", - "name-candle-style": "", - "name-close": "", - "name-color-strategy": "", - "name-down-color": "", - "name-high": "", - "name-low": "", - "name-mode": "", - "name-open": "", - "name-up-color": "", - "name-volume": "" + "name-additional-fields": "Bidang tambahan", + "name-candle-style": "Gaya lilin", + "name-close": "Tutup", + "name-color-strategy": "Strategi warna", + "name-down-color": "Warna penurunan", + "name-high": "Tinggi", + "name-low": "Rendah", + "name-mode": "Mode", + "name-open": "Buka", + "name-up-color": "Warna kenaikan", + "name-volume": "Volume" }, "canvas": { "apieditor": { @@ -3631,18 +3659,18 @@ } }, "button-item": { - "category-button": "", + "category-button": "Tombol", "label": { "center": "Pusat", "left": "Kiri", "right": "Kanan" }, - "name-align-text": "", - "name-api": "", - "name-style": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", + "name-align-text": "Ratakan teks", + "name-api": "API", + "name-style": "Gaya tampilan", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", "placeholder": { "auto": "Auto" } @@ -3669,20 +3697,20 @@ } } }, - "category-arrow-direction": "", - "category-background": "", - "category-border": "", - "category-canvas": "", - "category-color": "", - "category-data-links": "", - "category-line-style": "", - "category-metric-value": "", - "category-radius": "", - "category-selected-connection": "", - "category-selected-element": "", - "category-size": "", + "category-arrow-direction": "Arah Panah", + "category-background": "Latar Belakang", + "category-border": "Bingkai", + "category-canvas": "Kanvas", + "category-color": "Warna", + "category-data-links": "Tautan data dan tindakan", + "category-line-style": "Gaya tampilan garis", + "category-metric-value": "Nilai metrik", + "category-radius": "Radius", + "category-selected-connection": "Koneksi yang dipilih", + "category-selected-element": "Elemen yang dipilih ({{element}})", + "category-size": "Ukuran", "cloud-item": { - "category-cloud": "", + "category-cloud": "Cloud", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3691,48 +3719,48 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } }, - "description-experimental-types": "", - "description-infinite-panning": "", - "description-inline-editing": "", - "description-pan-zoom": "", + "description-experimental-types": "Aktifkan pemilihan jenis elemen eksperimental", + "description-infinite-panning": "Aktifkan fitur geser tak terbatas - berguna untuk kanvas yang luas. Peringatan: ini adalah fitur eksperimental dan saat ini hanya berfungsi dengan baik dengan elemen yang dibatasi terhadap sudut atas/kiri", + "description-inline-editing": "Aktifkan pengeditan panel secara langsung", + "description-pan-zoom": "Aktifkan geser dan zoom", "direction-options": { - "label-both": "", - "label-forward": "", - "label-none": "", - "label-reverse": "" + "label-both": "Keduanya", + "label-forward": "Teruskan", + "label-none": "Tidak ada", + "label-reverse": "Balik" }, "drone-front-item": { - "category-drone-front": "", - "name-roll-angle": "" + "category-drone-front": "Bagian Depan Drone", + "name-roll-angle": "Sudut Putar" }, "drone-side-item": { - "category-drone-side": "", - "name-pitch-angle": "" + "category-drone-side": "Bagian Samping Drone", + "name-pitch-angle": "Sudut Pitch" }, "drone-top-item": { - "category-drone-top": "", - "name-back-left-rotor-rpm": "", - "name-back-right-rotor-rpm": "", - "name-front-left-rotor-rpm": "", - "name-front-right-rotor-rpm": "", - "name-yaw-angle": "" + "category-drone-top": "Bagian Atas Drone", + "name-back-left-rotor-rpm": "RPM Rotor Belakang Kiri", + "name-back-right-rotor-rpm": "RPM Rotor Belakang Kanan", + "name-front-left-rotor-rpm": "RPM Rotor Depan Kiri", + "name-front-right-rotor-rpm": "RPM Rotor Depan Kanan", + "name-yaw-angle": "Sudut Yaw" }, "element-editor": { - "category-layout": "", - "description-unsupported": "", - "name-quick-placement": "" + "category-layout": "Tata Letak", + "description-unsupported": "Jenis elemen yang dipilih tidak didukung oleh pengaturan saat ini. Aktifkan jenis elemen lanjutan.", + "name-quick-placement": "Penempatan cepat" }, "ellipse-item": { - "category-ellipse": "", + "category-ellipse": "Elips", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3741,50 +3769,50 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } }, "icon-item": { - "category-icon": "", - "name-fill-color": "", - "name-svg-path": "" + "category-icon": "Ikon", + "name-fill-color": "Warna isian", + "name-svg-path": "Jalur SVG" }, "inline-edit": { "canvas-inline-editor": "Editor Inline Canvas", "tooltip-close-inline-editor": "Tutup editor inline" }, "inline-edit-body": { - "category-selected-element": "", + "category-selected-element": "Elemen yang dipilih ({{element}})", "label-add-item": "Tambahkan item", "please-select-an-element": "Pilih elemen" }, - "label-actions": "", - "label-color": "", - "label-direction": "", - "label-image": "", - "label-image-size": "", - "label-line-style": "", - "label-links": "", - "label-radius": "", - "label-size": "", - "label-width": "", + "label-actions": "Tindakan", + "label-color": "Warna", + "label-direction": "Arah", + "label-image": "Gambar", + "label-image-size": "Ukuran gambar", + "label-line-style": "Gaya tampilan garis", + "label-links": "Tautan", + "label-radius": "Radius", + "label-size": "Ukuran", + "label-width": "Lebar", "layer-editor": { - "category-layer": "", - "category-layout": "", - "name-constraints": "", - "name-elements": "" + "category-layer": "Layer", + "category-layout": "Tata Letak", + "name-constraints": "Kendala", + "name-elements": "Elemen" }, "line-style-editor": { "label-animate": "Animasikan" }, "line-style-options": { - "label-solid": "" + "label-solid": "Solid" }, "metric-value-item": { "label": { @@ -3799,15 +3827,15 @@ "auto": "Auto" } }, - "name-align-text": "", - "name-color": "", - "name-experimental-types": "", - "name-infinite-panning": "", - "name-inline-editing": "", - "name-pan-zoom": "", - "name-text": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-color": "Warna teks", + "name-experimental-types": "Jenis elemen eksperimental", + "name-infinite-panning": "Geser tak terbatas", + "name-inline-editing": "Pengeditan inline", + "name-pan-zoom": "Geser dan zoom", + "name-text": "Teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "not-found-display": { "not-found": "<0>Tidak ditemukan: " }, @@ -3820,7 +3848,7 @@ "zoom-scroll-wheel": "Zoom: Roda gulir" }, "parallelogram-item": { - "category-parallelogram": "", + "category-parallelogram": "Jajaran genjang", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3829,11 +3857,11 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } @@ -3846,21 +3874,21 @@ }, "placement-editor": { "horizontal-options": { - "label-center": "", - "label-left": "", - "label-left-and-right": "", - "label-right": "", - "label-scale": "" + "label-center": "Pusat", + "label-left": "Kiri", + "label-left-and-right": "Kiri & kanan", + "label-right": "Kanan", + "label-scale": "Skala" }, "label-constraints": "Kendala", "label-position": "Jabatan", "loading": "Memuat...", "vertical-options": { - "label-bottom": "", - "label-center": "", - "label-scale": "", - "label-top": "", - "label-top-and-bottom": "" + "label-bottom": "Bawah", + "label-center": "Pusat", + "label-scale": "Skala", + "label-top": "Atas", + "label-top-and-bottom": "Atas & bawah" } }, "quick-positioning": { @@ -3872,7 +3900,7 @@ "tooltip-align-vertical-centers": "Ratakan ke tengah vertikal" }, "rectangle-item": { - "category-rectangle": "", + "category-rectangle": "Persegi panjang", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3881,33 +3909,33 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } }, "server-item": { - "category-server": "", - "name-blink-rate": "", - "name-bulb-color": "", - "name-status-color": "", - "name-type": "", + "category-server": "Server", + "name-blink-rate": "Frekuensi kedipan [hz] (0 = mati)", + "name-bulb-color": "Warna bohlam", + "name-status-color": "Warna status", + "name-type": "Jenis", "type-options": { - "label-database": "", - "label-single": "", - "label-stack": "", - "label-terminal": "" + "label-database": "Basis data", + "label-single": "Tunggal", + "label-stack": "Tumpukan", + "label-terminal": "Terminal" } }, "text-display": { "double-click-to-set": "Klik dua kali untuk mengatur teks" }, "text-item": { - "category-text": "", + "category-text": "Teks", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3916,11 +3944,11 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } @@ -3942,7 +3970,7 @@ "tooltip-remove": "Hapus" }, "triangle-item": { - "category-triangle": "", + "category-triangle": "Segitiga", "label": { "bottom": "Bawah", "center": "Pusat", @@ -3951,18 +3979,18 @@ "right": "Kanan", "top": "Atas" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "Ratakan teks", + "name-text": "Teks", + "name-text-color": "Warna teks", + "name-text-size": "Ukuran teks", + "name-vertical-align": "Rata vertikal", "placeholder": { "auto": "Auto" } }, "wind-turbine-item": { - "category-wind-turbine": "", - "name-rpm": "" + "category-wind-turbine": "Turbin Angin", + "name-rpm": "RPM" } }, "carousel": { @@ -3979,8 +4007,8 @@ "close-button": { "tooltip": "Tutup" }, - "cloud-enterprise-feature-badge": "", - "cloud-feature-badge": "", + "cloud-enterprise-feature-badge": "Cloud & Enterprise", + "cloud-feature-badge": "Cloud", "combobox": { "async": { "error": "Terjadi kesalahan saat memuat opsi." @@ -4510,7 +4538,7 @@ } }, "description": { - "action": "" + "action": "Ubah deskripsi dasbor" }, "dynamic-config-value-editor": { "render-label": { @@ -4519,12 +4547,16 @@ }, "edit-actions": { "add": "Tambahkan {{typeName}}", - "move": "", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", + "move": "Pindahkan {{typeName}}", "panel-background": "Ubah latar belakang panel", "panel-description": "Ubah deskripsi panel", - "panel-max-repeats-per-row": "", + "panel-max-repeats-per-row": "Pengulangan maksimum per baris", "panel-repeat-direction": "Ulangi arah", - "panel-repeat-variable": "", + "panel-repeat-variable": "Panel diulang berdasarkan", "panel-title": "Ubah judul panel", "paste-panel": "Tempel panel", "remove": "Hapus {{typeName}}", @@ -4533,6 +4565,7 @@ "edit-pane": { "elements": { "dashboard": "Dasbor", + "element": "", "local-variable": "Variabel lokal", "multiple-elements": "Beberapa elemen", "multiple-elements-delete-text": "Anda yakin ingin menghapus elemen ini?", @@ -4658,7 +4691,7 @@ "get-field-override-categories": { "label-add-field-override": "Tambah penimpaan bidang", "label-add-override-property": "Tambah properti penimpaan", - "override-name": "", + "override-name": "Timpa {{overrideNum}}", "title": { "add-button": "tambahkan tombol" } @@ -4882,7 +4915,7 @@ "title-matched_other": "Cocok {{count}}/{{totalCount}} opsi" }, "outline": { - "repeated-item": "", + "repeated-item": "Ulangi", "tree-item": { "empty": "(kosong)", "no-title": "" @@ -5040,8 +5073,8 @@ "label-changes": "Perubahan", "label-details": "Detail", "title": "Simpan dasbor", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Simpan salinan dasbor", + "title-provisioned": "Dasbor yang disediakan" }, "save-dashboard-error-proxy": { "body-name-exists": "Dasbor dengan nama yang sama di folder yang dipilih sudah ada.<1><2>Apa Anda masih ingin menyimpan dasbor ini?", @@ -5118,7 +5151,7 @@ } }, "title": { - "action": "" + "action": "Ubah judul dasbor" }, "toolbar": { "add": "Tambahkan", @@ -5553,12 +5586,12 @@ } }, "delete-provisioned-dashboard-form": { - "api-error": "", + "api-error": "Gagal menghapus dasbor", "cancel-action": "Batalkan", "delete-action": "Hapus dasbor", - "delete-read-only-file-message": "", + "delete-read-only-file-message": "Dasbor ini tidak dapat dihapus langsung dari Grafana karena repositori bersifat hanya-baca. Untuk menghapus dasbor ini, harap hapus file dari repositori Git Anda.", "deleting": "Menghapus...", - "drawer-title": "", + "drawer-title": "Hapus Dasbor yang Disediakan", "title-this-repository-is-read-only": "Repositori ini hanya dapat dibaca" }, "description-label": { @@ -5572,7 +5605,7 @@ } }, "edit-provisioned-dashboard-form": { - "success": "" + "success": "Perubahan dasbor berhasil disimpan" }, "email-list": { "aria-label-emailmenu": "Alihkan tombol menu email" @@ -5740,7 +5773,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5765,7 +5797,7 @@ } }, "panel-data-alerting-tab": { - "tab-label": "" + "tab-label": "Peringatan" }, "panel-data-alerting-tab-rendered": { "alert": { @@ -5776,14 +5808,14 @@ "title-dashboard-not-saved": "Dasbor tidak disimpan" }, "panel-data-queries-tab": { - "tab-label": "" + "tab-label": "Kueri" }, "panel-data-queries-tab-rendered": { "add-query": "Tambahkan kueri", "expression": "Pola " }, "panel-data-transformations-tab": { - "tab-label": "" + "tab-label": "Transformasi" }, "panel-data-transformations-tab-rendered": { "add-another-transformation": "Tambah transformasi lain", @@ -5886,8 +5918,8 @@ "label-changes": "Perubahan", "label-details": "Detail", "title": "Simpan dasbor", - "title-copy": "", - "title-provisioned": "" + "title-copy": "Simpan salinan dasbor", + "title-provisioned": "Dasbor yang disediakan" } }, "save-dashboard-form": { @@ -5906,8 +5938,8 @@ "title-someone-else-has-updated-this-dashboard": "Orang lain telah memperbarui dasbor ini", "would-still-dashboard": "Ingin tetap menyimpan dasbor ini?" }, - "schema-v2-library-panels-export": "Fungsionalitas dasbor dinamis bersifat eksperimen, dan tidak memiliki paritas fitur penuh dengan perilaku dasbor saat ini. Ini didasarkan pada format skema baru, yang tidak mendukung panel pustaka. Ini berarti ketika mengekspor dasbor untuk menggunakannya di instans lain, kami tidak akan menyertakan panel pustaka. Kami bermaksud untuk mendukung seiring fitur <2>siklus pengembangan fitur.", - "schema-v2-library-panels-export-title": "Skema Dasbor V2 belum mendukung ekspor panel pustaka untuk digunakan di instans lain", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Perubahan pada dasbor tidak dapat dipulihkan" }, "save-dashboard-form-common-options": { @@ -6210,18 +6242,18 @@ } }, "dashlist": { - "category-dashboard-list": "", - "name-folder": "", - "name-include-current-template-variables": "", - "name-include-current-time-range": "", - "name-max-items": "", - "name-query": "", - "name-recently-viewed": "", - "name-search": "", - "name-show-folder-names": "", - "name-show-headings": "", - "name-starred": "", - "name-tags": "" + "category-dashboard-list": "Daftar dasbor", + "name-folder": "Folder", + "name-include-current-template-variables": "Sertakan nilai variabel templat saat ini", + "name-include-current-time-range": "Sertakan rentang waktu saat ini", + "name-max-items": "Item maks", + "name-query": "Kueri", + "name-recently-viewed": "Baru dilihat", + "name-search": "Cari", + "name-show-folder-names": "Tampilkan nama folder", + "name-show-headings": "Tampilkan judul", + "name-starred": "Diberi bintang", + "name-tags": "Tag" }, "data-source-list": { "empty-state": { @@ -6483,15 +6515,15 @@ "dimensions": { "background-size-editor": { "options": { - "label-contain": "", - "label-cover": "", - "label-fill": "", - "label-original": "", - "label-tile": "" + "label-contain": "Berisi", + "label-cover": "Menutupi", + "label-fill": "Mengisi", + "label-original": "Asli", + "label-tile": "Tile" } }, "color-dimension-editor": { - "label-fixed-color": "", + "label-fixed-color": "Warna tetap", "noOptionsMessage-no-fields-found": "" }, "file-dropzone-custom-children": { @@ -6506,10 +6538,10 @@ "placeholder-search": "Cari" }, "resource-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Gunakan hasil bidang string", + "description-fixed": "Nilai tetap", "label-field": "Bidang", - "label-fixed": "", + "label-fixed": "Tetap", "label-mappings": "Pemetaan", "label-source": "Sumber" }, @@ -6526,16 +6558,16 @@ }, "scalar-dimension-editor": { "fixed-value-options": { - "label-fixed-values": "" + "label-fixed-values": "Nilai tetap" }, "label-limit": "Batas", "label-value": "Nilai", "noOptionsMessage-no-fields-found": "", "scalar-options": { - "description-clamped": "", - "description-mod": "", - "label-clamped": "", - "label-mod": "" + "description-clamped": "Gunakan nilai bidang, terbatas ke maks dan min", + "description-mod": "Gunakan nilai bidang, mod dari maks", + "label-clamped": "Terbatas", + "label-mod": "Mod" } }, "scale-dimension-editor": { @@ -6550,10 +6582,10 @@ "noOptionsMessage-no-fields-found": "" }, "text-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "Nilai bidang tampilan", + "description-fixed": "Nilai tetap", "label-field": "Bidang", - "label-fixed": "", + "label-fixed": "Tetap", "label-source": "Sumber", "label-template": "Templat", "label-value": "Nilai" @@ -6567,16 +6599,16 @@ "description-percentage-means-thresholds-relative": "Persentase berarti ambang batas terkait min & maks", "modes": { "description": { - "threshold-based-percent-between-minmax": "", - "thresholds-based-absolute-values": "" + "threshold-based-percent-between-minmax": "Pilih ambang batas berdasarkan persentase antara min/maks", + "thresholds-based-absolute-values": "Pilih ambang batas berdasarkan nilai absolut" }, "label": { - "absolute": "", - "percentage": "" + "absolute": "Absolut", + "percentage": "Persentase" } }, "thresholds-mode": "Mode ambang batas", - "value-base": "" + "value-base": "Dasar" }, "urlpicker-tab": { "label-preview": "Pratinjau" @@ -7110,6 +7142,7 @@ "start-time": "Waktu Mulai:" } }, + "share-span": "", "warnings": "Peringatan" }, "span-filters": { @@ -7377,23 +7410,23 @@ "title": "Keluar dari halaman?" }, "gauge": { - "category-gauge": "", - "description-min-height": "", - "description-min-width": "", - "description-neutral": "", - "description-show-threshold-labels": "", - "description-show-threshold-markers": "", + "category-gauge": "Alat ukur", + "description-min-height": "Tinggi baris minimum (orientasi horizontal)", + "description-min-width": "Lebar kolom minimum (orientasi vertikal)", + "description-neutral": "Biarkan kosong untuk menggunakan Min sebagai titik netral", + "description-show-threshold-labels": "Tampilkan nilai ambang batas di sekitar bilah pengukur", + "description-show-threshold-markers": "Menampilkan ambang batas sebagai bilah luar", "gauge-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Otomatis", + "label-manual": "Manual" }, - "name-gauge-size": "", - "name-min-height": "", - "name-min-width": "", - "name-neutral": "", - "name-show-threshold-labels": "", - "name-show-threshold-markers": "", - "placeholder-neutral": "" + "name-gauge-size": "Ukuran alat ukur", + "name-min-height": "Tinggi min", + "name-min-width": "Lebar minimum", + "name-neutral": "Netral", + "name-show-threshold-labels": "Tampilkan label ambang batas", + "name-show-threshold-markers": "Tampilkan penanda ambang batas", + "placeholder-neutral": "otomatis" }, "gen-ai": { "apply-suggestion": "Terapkan", @@ -7689,6 +7722,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7727,85 +8163,85 @@ }, "builder": { "axis": { - "category-axis": "", - "color-label": "", + "category-axis": "Sumbu", + "color-label": "Warna", "color-options": { - "label-series": "", - "label-text": "" + "label-series": "Data seri", + "label-text": "Teks" }, "grid-line-options": { - "label-auto": "", - "label-off": "", - "label-on": "" + "label-auto": "Otomatis", + "label-off": "Nonaktif", + "label-on": "Aktif" }, - "name-centered-zero": "", - "name-grid-lines": "", - "name-label": "", - "name-placement": "", - "name-scale": "", - "name-show-border": "", - "name-soft-max": "", - "name-soft-min": "", - "name-width": "", - "placeholder-label": "", - "placeholder-soft-max": "", - "placeholder-soft-min": "", - "placeholder-width": "", + "name-centered-zero": "Nol tengah", + "name-grid-lines": "Tampilkan garis kisi", + "name-label": "Label", + "name-placement": "Penempatan", + "name-scale": "Skala", + "name-show-border": "Tampilkan batas", + "name-soft-max": "Maksimum lunak", + "name-soft-min": "Minimum lunak", + "name-width": "Lebar", + "placeholder-label": "Teks opsional", + "placeholder-soft-max": "Lihat: Opsi standar > Maks", + "placeholder-soft-min": "Lihat: Opsi standar > Min", + "placeholder-width": "Otomatis", "scale-distribution-editor": { "distribution-options": { - "label-linear": "", - "label-log": "", - "label-symlog": "" + "label-linear": "Linier", + "label-log": "Logaritma", + "label-symlog": "Symlog" } } }, "legend": { - "category": "", - "description-values": "", + "category": "Legenda", + "description-values": "Pilih nilai atau perhitungan untuk ditampilkan dalam legenda", "mode-options": { - "label-list": "", - "label-table": "" + "label-list": "Daftar", + "label-table": "Tabel" }, - "name-mode": "", - "name-placement": "", - "name-values": "", - "name-visibility": "", - "name-width": "", + "name-mode": "Mode", + "name-placement": "Penempatan", + "name-values": "Nilai", + "name-visibility": "Visibilitas", + "name-width": "Lebar", "placement-options": { - "label-bottom": "", - "label-right": "" + "label-bottom": "Bawah", + "label-right": "Kanan" } }, "stacking": { - "name-stack-series": "" + "name-stack-series": "Seri tumpukan" }, "text": { - "category-text-size": "", - "name-percent-change": "", - "name-title": "", - "name-value": "", - "placeholder-percent-change": "", - "placeholder-title": "", - "placeholder-value": "" + "category-text-size": "Ukuran teks", + "name-percent-change": "Perubahan persen", + "name-title": "Judul", + "name-value": "Nilai", + "placeholder-percent-change": "Otomatis", + "placeholder-title": "Otomatis", + "placeholder-value": "Otomatis" }, "tooltip": { - "category": "", - "description-hover-proximity": "", + "category": "Tooltip", + "description-hover-proximity": "Seberapa dekat kursor harus berada ke titik untuk memicu tooltip, dalam piksel", "modeOptions": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Semua", + "label-hidden": "Tersembunyi", + "label-single": "Tunggal" }, - "name-hide-zeros": "", - "name-hover-proximity": "", - "name-max-height": "", - "name-max-width": "", - "name-tooltip-mode": "", - "name-values-sort-order": "", + "name-hide-zeros": "Sembunyikan nol", + "name-hover-proximity": "Kedekatan hover", + "name-max-height": "Tinggi maks", + "name-max-width": "Lebar maks", + "name-tooltip-mode": "Mode tooltip", + "name-values-sort-order": "Urutan penyortiran nilai", "sortOptions": { - "label-ascending": "", - "label-descending": "", - "label-none": "" + "label-ascending": "Naik", + "label-descending": "Turun", + "label-none": "Tidak ada" } } }, @@ -7928,6 +8364,60 @@ "previous-label": "Bulan sebelumnya", "select-placeholder": "Pilih tanggal/waktu" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Tutup" }, @@ -7967,19 +8457,33 @@ "tooltip-expand-all": "Perluas semua baris" }, "matchers-ui": { - "description-field-name-by-regex-matcher": "", - "description-fields-by-query": "", - "description-fields-with-name": "", - "description-fields-with-type": "", - "description-fields-with-value": "", + "description-field-name-by-regex-matcher": "Atur properti untuk bidang dengan nama yang cocok dengan regex", + "description-fields-by-query": "Atur properti untuk bidang dari kueri tertentu", + "description-fields-with-name": "Atur properti untuk bidang tertentu", + "description-fields-with-type": "Atur properti untuk bidang dari jenis tertentu (angka, string, boolean)", + "description-fields-with-value": "Atur properti untuk bidang dengan kondisi reducer", "field-name-picker": { - "placeholder": "" + "placeholder": "Pilih bidang" }, - "name-field-name-by-regex-matcher": "", - "name-fields-by-query": "", - "name-fields-with-name": "", - "name-fields-with-type": "", - "name-fields-with-value": "" + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, + "name-field-name-by-regex-matcher": "Bidang dengan nama yang cocok dengan regex", + "name-fields-by-query": "Bidang yang dikembalikan oleh kueri", + "name-fields-with-name": "Bidang dengan nama", + "name-fields-with-type": "Bidang dengan jenis", + "name-fields-with-value": "Bidang dengan nilai" }, "menu-item": { "keyboard-shortcut-label": "Pintasan keyboard" @@ -8079,7 +8583,10 @@ "no-data": "" }, "no-values-label": "Tidak ada nilai", - "pagination-summary": "{{itemsRangeStart}}—{{displayedEnd}} dari {{numRows}} baris" + "pagination-summary": "{{itemsRangeStart}}—{{displayedEnd}} dari {{numRows}} baris", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tag" @@ -8103,55 +8610,55 @@ "config": { "get-graph-field-options": { "axis-placement": { - "description-auto": "", - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-right": "" + "description-auto": "Bidang pertama di sebelah kiri, yang lainnya di sebelah kanan", + "label-auto": "Otomatis", + "label-hidden": "Tersembunyi", + "label-left": "Kiri", + "label-right": "Kanan" }, "bar-alignment": { - "description-after": "", - "description-before": "", - "description-center": "" + "description-after": "Setelah", + "description-before": "Sebelum", + "description-center": "Pusat" }, "draw-style": { - "label-bars": "", - "label-lines": "", - "label-points": "" + "label-bars": "Bilah", + "label-lines": "Garis", + "label-points": "Poin" }, "fill-gradient": { - "description-hue": "", - "description-opacity": "", - "description-scheme": "", - "label-hue": "", - "label-none": "", - "label-opacity": "", - "label-scheme": "" + "description-hue": "Gradien rona warna kecil", + "description-opacity": "Aktifkan gradien opasitas isian", + "description-scheme": "Gunakan skema warna untuk menentukan gradien", + "label-hue": "Rona", + "label-none": "Tidak ada", + "label-opacity": "Opasitas", + "label-scheme": "Skema" }, "line-interpolation": { - "description-linear": "", - "description-smooth": "", - "description-step-after": "", - "description-step-before": "" + "description-linear": "Linier", + "description-smooth": "Halus", + "description-step-after": "Langkah setelah", + "description-step-before": "Langkah sebelum" }, "show-points": { - "description-auto": "", - "label-always": "", - "label-auto": "", - "label-never": "" + "description-auto": "Tampilkan titik-titik saat densitasnya rendah", + "label-always": "Selalu", + "label-auto": "Otomatis", + "label-never": "Jangan pernah" }, "stacking": { - "label-100": "", - "label-normal": "", - "label-off": "" + "label-100": "100%", + "label-normal": "Normal", + "label-off": "Nonaktif" }, "thresholds-display-mode": { - "label-dashed-lines": "", - "label-filled-regions": "", - "label-filled-regions-and-dashed-lines": "", - "label-filled-regions-and-lines": "", - "label-lines": "", - "label-off": "" + "label-dashed-lines": "Sebagai garis (berpola putus-putus)", + "label-filled-regions": "Sebagai wilayah yang terisi", + "label-filled-regions-and-dashed-lines": "Sebagai area dan garis yang terisi (berpola putus-putus)", + "label-filled-regions-and-lines": "Sebagai area dan garis yang terisi", + "label-lines": "Sebagai garis", + "label-off": "Nonaktif" } } } @@ -8198,78 +8705,78 @@ }, "heatmap": { "calculate-from-data-options": { - "label-no": "", - "label-yes": "" + "label-no": "Tidak", + "label-yes": "Ya" }, - "category-cell-display": "", - "category-colors": "", - "category-exemplars": "", - "category-heatmap": "", - "category-legend": "", - "category-tooltip": "", - "category-y-axis": "", + "category-cell-display": "Tampilan sel", + "category-colors": "Warna", + "category-exemplars": "Contoh", + "category-heatmap": "Peta panas", + "category-legend": "Legenda", + "category-tooltip": "Tooltip", + "category-y-axis": "Sumbu Y", "mode-options": { - "label-opacity": "", - "label-scheme": "" + "label-opacity": "Opasitas", + "label-scheme": "Skema" }, - "name-axis-label": "", - "name-axis-width": "", - "name-calculate-from-data": "", - "name-cell-gap": "", - "name-color": "", - "name-decimals": "", - "name-end-color-at-value": "", - "name-exponent": "", - "name-hide-cells-gt": "", - "name-hide-cells-lt": "", - "name-max-height": "", - "name-max-value": "", - "name-max-width": "", - "name-min-value": "", - "name-mode": "", - "name-placement": "", - "name-reverse": "", - "name-scale": "", - "name-scheme": "", - "name-show-color-scale": "", - "name-show-histogram": "", - "name-show-legend": "", - "name-start-color-from-value": "", - "name-steps": "", - "name-tick-alignment": "", - "name-tooltip-mode": "", - "name-unit": "", - "name-value-name": "", - "name-y-axis-scale": "", - "placeholder-axis-label": "", - "placeholder-axis-width": "", - "placeholder-decimals": "", - "placeholder-end-color-at-value": "", - "placeholder-hide-cells-gt": "", - "placeholder-hide-cells-lt": "", - "placeholder-max-value": "", - "placeholder-min-value": "", - "placeholder-start-color-from-value": "", - "placeholder-value-name": "", + "name-axis-label": "Label sumbu", + "name-axis-width": "Lebar sumbu", + "name-calculate-from-data": "Hitung dari data", + "name-cell-gap": "Jarak sel", + "name-color": "Warna", + "name-decimals": "Desimal", + "name-end-color-at-value": "Akhiri skala warna pada nilai", + "name-exponent": "Eksponen", + "name-hide-cells-gt": "Sembunyikan sel dengan nilai >=", + "name-hide-cells-lt": "Sembunyikan sel dengan nilai <=", + "name-max-height": "Tinggi maks", + "name-max-value": "Nilai maks", + "name-max-width": "Lebar maks", + "name-min-value": "Nilai min", + "name-mode": "Mode", + "name-placement": "Penempatan", + "name-reverse": "Balik", + "name-scale": "Skala", + "name-scheme": "Skema", + "name-show-color-scale": "Tampilkan skala warna", + "name-show-histogram": "Tampilkan histogram (sumbu Y)", + "name-show-legend": "Tampilkan keterangan", + "name-start-color-from-value": "Mulai skala warna dari nilai", + "name-steps": "Langkah", + "name-tick-alignment": "Perataan garis centang", + "name-tooltip-mode": "Mode tooltip", + "name-unit": "Unit", + "name-value-name": "Nama nilai", + "name-y-axis-scale": "Skala sumbu Y", + "placeholder-axis-label": "Otomatis", + "placeholder-axis-width": "Otomatis", + "placeholder-decimals": "Otomatis", + "placeholder-end-color-at-value": "Otomatis (maks)", + "placeholder-hide-cells-gt": "Tidak ada", + "placeholder-hide-cells-lt": "Tidak ada", + "placeholder-max-value": "Otomatis", + "placeholder-min-value": "Otomatis", + "placeholder-start-color-from-value": "Otomatis (min)", + "placeholder-value-name": "Nilai", "placement-options": { - "label-hidden": "", - "label-left": "", - "label-right": "" + "label-hidden": "Tersembunyi", + "label-left": "Kiri", + "label-right": "Kanan" }, "scale-options": { - "label-exponential": "", - "label-linear": "" + "label-exponential": "Eksponensial", + "label-linear": "Linier" }, "tick-alignment-options": { - "label-auto": "", - "label-bottom": "", - "label-middle": "", - "label-top": "" + "label-auto": "Otomatis", + "label-bottom": "Bawah (GE)", + "label-middle": "Tengah", + "label-top": "Atas (LE)" }, "tooltip-mode-options": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "Semua", + "label-hidden": "Tersembunyi", + "label-single": "Tunggal" } }, "help-modal": { @@ -8329,14 +8836,14 @@ "troubleshooting-help": "Untuk meminta bantuan pemecahan masalah, kirim snapshot panel ini ke Dukungan Teknis Grafana Labs. Snapshot berisi data respons kueri dan pengaturan panel." }, "histogram": { - "category-histogram": "", + "category-histogram": "Histogram", "histogram-panel": { "no-histogram-found-in-response": "Tidak ada histogram yang ditemukan dalam respons" }, - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-width": "", - "name-stacking": "" + "name-fill-opacity": "Opasitas isian", + "name-gradient-mode": "Mode gradien", + "name-line-width": "Lebar garis", + "name-stacking": "Tumpukan" }, "inspector": { "inspect-data-tab": { @@ -8712,16 +9219,16 @@ } }, "logs": { - "category-logs": "", + "category-logs": "Log", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Tepat", + "label-none": "Tidak ada", + "label-numbers": "Angka", + "label-signature": "Tanda Tangan" }, - "description-enable-infinite-scrolling": "", - "description-enable-syntax-highlighting": "", - "description-show-controls": "", + "description-enable-infinite-scrolling": "Eksperimental. Minta lebih banyak hasil dengan menggulir ke bagian bawah daftar log.", + "description-enable-syntax-highlighting": "Gunakan tata bahasa pewarnaan sintaks yang telah ditentukan untuk menyoroti bagian yang relevan dari baris log", + "description-show-controls": "Tampilkan kontrol untuk melompat ke baris log terakhir atau pertama, dan filter berdasarkan level log", "fields": { "type": { "loki": { @@ -8732,8 +9239,8 @@ } }, "font-size-options": { - "label-default": "", - "label-small": "" + "label-default": "Default", + "label-small": "Kecil" }, "get-dataframe-fields": { "vars": { @@ -8780,58 +9287,64 @@ "tooltip-error": "Kesalahan: {{errorMessage}}" }, "log-line-details": { - "clear-search": "", - "close": "", - "copy-shortlink": "", - "copy-to-clipboard": "", + "clear-search": "Hapus", + "close": "Tutup detail log", + "copy-shortlink": "Salin tautan pendek", + "copy-to-clipboard": "Salin ke papan klip", "displayed-fields-section": "", "fields": { - "adhoc-statistics": "", - "copy-value-to-clipboard": "", - "filter-out": "", - "filter-out-query": "", + "adhoc-statistics": "Statistik ad-hoc", + "copy-value-to-clipboard": "Salin nilai ke papan klip", + "filter-out": "Filter nilai", + "filter-out-query": "Filter nilai dalam kueri {{query}}", "toggle-field-button": { - "field-instead-message": "", - "hide-this-field": "" + "field-instead-message": "Tampilkan bidang ini, bukan pesan", + "hide-this-field": "Sembunyikan bidang ini" } }, - "fields-section": "", - "hide-log-line": "", + "fields-section": "Bidang", + "hide-log-line": "Sembunyikan baris log", "inline-mode": "", - "links-section": "", + "links-section": "Tautan", "log-line-field": "", - "log-line-section": "", + "log-line-section": "Baris log", "move-displayed-field-down": "", "move-displayed-field-up": "", - "no-details": "", - "pin-line": "", + "no-details": "Tidak ada bidang untuk ditampilkan.", + "open-assistant": "", + "pin-line": "Sematkan log", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { - "no-results": "" + "no-results": "Tidak ada hasil untuk ditampilkan." }, - "search-placeholder": "", - "show-context": "", - "show-log-line": "", + "search-placeholder": "Cari nama dan nilai bidang", + "show-context": "Tampilkan konteks", + "show-log-line": "Tampilkan baris log", "sidebar-mode": "", - "unpin-line": "" + "unpin-line": "Buka sematan log" }, "log-line-menu": { "copy-link": "Salin tautan ke baris log", "copy-log": "Salin baris log", "hide-details": "Tampilkan detail log", "icon-label": "Menu log", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Sematkan log", "show-context": "Tampilkan konteks", "show-details": "Sembunyikan detail log", "unpin-from-outline": "Buka sematan log" }, "log-list-search": { - "close": "", - "filter": "", - "info": "", - "input-placeholder": "", - "next": "", - "prev": "" + "close": "Tutup pencarian", + "filter": "Filter log yang cocok", + "info": "Pencarian sisi klien untuk string dalam log yang ditampilkan. Jangan sampai tertukar dengan filter kueri. Gunakan komponen ini untuk mencari string tertentu dalam hasil log Anda.", + "input-placeholder": "Cari di log", + "next": "Hasil berikutnya", + "prev": "Hasil sebelumnya" }, "log-row-context-modal": { "error-loading-log-more-logs": "Kesalahan saat memuat lebih banyak log.", @@ -8886,7 +9399,7 @@ "escape-newlines": "Perbaiki escape sequence baris baru dan tab yang salah di baris log", "font-size-default": "Gunakan ukuran fon kecil", "font-size-small": "Gunakan ukuran fon default", - "hide-search": "", + "hide-search": "Tutup pencarian", "hide-timestamps": "Sembunyikan stempel waktu", "hide-unique-labels": "Sembunyikan label unik", "newest-first": "Diurutkan berdasarkan log terbaru lebih dulu - Klik untuk menampilkan yang paling lama lebih dulu", @@ -8895,7 +9408,7 @@ "remove-escaping": "Hapus escape", "scroll-bottom": "Gulir ke bawah", "scroll-top": "Gulir ke atas", - "show-search": "", + "show-search": "Cari di hasil log", "show-timestamps": "Tampilkan stempel waktu", "show-unique-labels": "Tampilkan label unik", "unwrap-lines": "Batal terapkan wrap pada baris", @@ -8913,26 +9426,26 @@ "common-labels": "Label umum:" } }, - "name-common-labels": "", - "name-deduplication": "", + "name-common-labels": "Label umum", + "name-deduplication": "Deduplikasi", "name-details-mode": "", "name-details-options": { "label-inline": "", "label-sidebar": "" }, - "name-enable-infinite-scrolling": "", - "name-enable-log-details": "", - "name-enable-syntax-highlighting": "", - "name-font-size": "", - "name-order": "", - "name-prettify-json": "", - "name-show-controls": "", - "name-time": "", - "name-unique-labels": "", - "name-wrap-lines": "", + "name-enable-infinite-scrolling": "Aktifkan gulir tak terbatas", + "name-enable-log-details": "Aktifkan detail log", + "name-enable-syntax-highlighting": "Aktifkan penyorotan sintaks", + "name-font-size": "Ukuran font", + "name-order": "Urutan", + "name-prettify-json": "Rapikan JSON", + "name-show-controls": "Tampilkan kontrol", + "name-time": "Waktu", + "name-unique-labels": "Label unik", + "name-wrap-lines": "Bungkus baris", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Terbaru lebih dulu", + "label-oldest-first": "Terlama lebih dulu" }, "out-of-range-message": { "end-of-the-selected-time-range": "Akhir rentang waktu yang dipilih." @@ -8946,7 +9459,7 @@ "un-themed-log-details": { "aria-label-data-links": "Tautan data", "aria-label-fields": "Bidang", - "aria-label-line": "", + "aria-label-line": "Baris log", "aria-label-log-level": "Level log", "aria-label-no-details": "Tidak ada detail" }, @@ -8967,27 +9480,27 @@ } }, "logs-new": { - "category-logs": "", + "category-logs": "Log", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "Tepat", + "label-none": "Tidak ada", + "label-numbers": "Angka", + "label-signature": "Tanda Tangan" }, - "description-infinite-scrolling": "", - "description-show-controls": "", - "description-syntax-highlighting": "", - "name-deduplication": "", - "name-enable-log-details": "", - "name-infinite-scrolling": "", - "name-order": "", - "name-show-controls": "", - "name-syntax-highlighting": "", - "name-time": "", - "name-wrap-lines": "", + "description-infinite-scrolling": "Eksperimental. Minta lebih banyak hasil dengan menggulir ke bagian bawah daftar log.", + "description-show-controls": "Tampilkan kontrol untuk melompat ke baris log terakhir atau pertama, dan filter berdasarkan level log", + "description-syntax-highlighting": "Gunakan tata bahasa pewarnaan sintaks yang telah ditentukan untuk menyoroti bagian yang relevan dari baris log", + "name-deduplication": "Deduplikasi", + "name-enable-log-details": "Aktifkan detail log", + "name-infinite-scrolling": "Aktifkan gulir tak terbatas", + "name-order": "Urutan", + "name-show-controls": "Tampilkan kontrol", + "name-syntax-highlighting": "Aktifkan penyorotan sintaks", + "name-time": "Waktu", + "name-wrap-lines": "Bungkus baris", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "Terbaru lebih dulu", + "label-oldest-first": "Terlama lebih dulu" } }, "manage-dashbaords": { @@ -9653,7 +10166,9 @@ "aria-label": "Bantuan" }, "invite-user": { - "invite-new-member-button": "Undang anggota baru" + "invite-button": "Undang", + "invite-new-member-button": "Undang anggota baru", + "invite-tooltip": "" }, "item": { "add-bookmark": "Tambahkan ke Bookmark", @@ -9685,12 +10200,12 @@ "rss-button": "Terbaru dari blog" }, "news": { - "category-news": "", - "description-show-image": "", - "description-url": "", + "category-news": "Berita", + "description-show-image": "Mengontrol jika gambar sosial item berita (og:image) ditampilkan di atas konten teks", + "description-url": "Mendukung umpan RSS dan Atom", "link-title": "Buka blog Grafana labs", - "name-show-image": "", - "name-url": "", + "name-show-image": "Tampilkan gambar", + "name-url": "URL", "news-panel": { "body-error-loading-rss-feed": "Pastikan URL umpan sudah benar dan CORS dikonfigurasi dengan benar di server. Lihat <2>Dokumentasi panel berita.", "loading": "Memuat...", @@ -9699,27 +10214,27 @@ "title": "Terbaru dari blog" }, "node-graph": { - "category-edges": "", - "category-node-graph": "", - "category-nodes": "", + "category-edges": "Edge", + "category-node-graph": "Grafik node", + "category-nodes": "Node", "layout-algorithm-options": { - "description-force": "", - "description-grid": "", - "description-layered": "", - "label-force": "", - "label-grid": "", - "label-layered": "" + "description-force": "Gunakan tata letak berbasis gaya tarik-tolak", + "description-grid": "Gunakan tata letak kisi", + "description-layered": "Gunakan tata letak layer", + "label-force": "Gaya tarik-tolak", + "label-grid": "Kisi", + "label-layered": "Berlapis" }, - "name-arc-sections": "", - "name-layout-algorithm": "", - "name-main-stat-unit": "", - "name-secondary-stat-unit": "", - "name-zoom-mode": "", + "name-arc-sections": "Bagian busur", + "name-layout-algorithm": "Algoritme tata letak", + "name-main-stat-unit": "Unit stat utama", + "name-secondary-stat-unit": "Unit stat sekunder", + "name-zoom-mode": "Mode zoom", "zoom-mode-options": { - "description-cooperative": "", - "description-greedy": "", - "label-cooperative": "", - "label-greedy": "" + "description-cooperative": "Memungkinkan Anda menggulir halaman secara normal", + "description-greedy": "Bereaksi terhadap semua gerakan zoom", + "label-cooperative": "Kooperatif", + "label-greedy": "Serakah" } }, "nodeGraph": { @@ -9795,40 +10310,40 @@ }, "registry": { "get-all-option-editors": { - "description-time-zone": "", - "name-time-zone": "" + "description-time-zone": "Pemilihan zona waktu", + "name-time-zone": "Zona waktu" }, "standard-field-condigs": { - "category-data-links": "" + "category-data-links": "Tautan data dan tindakan" }, "standard-field-configs": { - "category": "", - "category-thresholds": "", - "category-value-mappings": "", - "description-display-name": "", - "description-field-min-max": "", - "description-max": "", - "description-min": "", - "description-no-value": "", - "description-value-mappings": "", - "name-actions": "", - "name-ad-hoc": "", - "name-color-scheme": "", - "name-data-links": "", - "name-decimals": "", - "name-display-name": "", - "name-field-min-max": "", - "name-max": "", - "name-min": "", - "name-no-value": "", - "name-thresholds": "", - "name-unit": "", - "name-value-mappings": "", - "placeholder-decimals": "", - "placeholder-display-name": "", - "placeholder-max": "", - "placeholder-min": "", - "placeholder-unit": "" + "category": "Opsi standar", + "category-thresholds": "Ambang batas", + "category-value-mappings": "Pemetaan nilai", + "description-display-name": "Ubah nama bidang atau seri", + "description-field-min-max": "Hitung min maks per bidang", + "description-max": "Biarkan kosong untuk menghitung berdasarkan semua nilai", + "description-min": "Biarkan kosong untuk menghitung berdasarkan semua nilai", + "description-no-value": "Yang ditampilkan ketika tidak ada nilai", + "description-value-mappings": "Ubah teks tampilan berdasarkan nilai input", + "name-actions": "Tindakan", + "name-ad-hoc": "Dapat difilter secara ad-hoc", + "name-color-scheme": "Skema warna", + "name-data-links": "Tautan data", + "name-decimals": "Desimal", + "name-display-name": "Nama tampilan", + "name-field-min-max": "Nilai min/maks bidang", + "name-max": "Maksimum", + "name-min": "Minimum", + "name-no-value": "Tidak ada nilai", + "name-thresholds": "Ambang batas", + "name-unit": "Unit", + "name-value-mappings": "Pemetaan nilai", + "placeholder-decimals": "otomatis", + "placeholder-display-name": "tidak ada", + "placeholder-max": "otomatis", + "placeholder-min": "otomatis", + "placeholder-unit": "tidak ada" } }, "units": { @@ -9937,13 +10452,13 @@ }, "panel-data-error-view": { "missing-value": { - "number": "", - "string": "", - "time": "", - "unknown": "" + "number": "Data tidak memiliki bidang angka", + "string": "Data tidak memiliki bidang string", + "time": "Data tidak memiliki bidang waktu", + "unknown": "Tidak dapat memvisualisasikan data" }, "no-value": { - "default": "" + "default": "Tidak ada data" }, "open-visualization-suggestions": "Buka saran visualisasi", "switch-to-table": "Alihkan ke tabel" @@ -9983,25 +10498,25 @@ "select-placeholder": "Filter berdasarkan jenis" }, "piechart": { - "category-legend": "", - "category-pie-chart": "", - "description-labels": "", - "description-pie-chart-type": "", + "category-legend": "Legenda", + "category-pie-chart": "Diagram lingkaran", + "description-labels": "Pilih label yang akan ditampilkan di diagram lingkaran", + "description-pie-chart-type": "Bagaimana diagram lingkaran harus ditampilkan", "labels-options": { - "label-name": "", - "label-percent": "", - "label-value": "" + "label-name": "Nama", + "label-percent": "Persen", + "label-value": "Nilai" }, "legend-values-options": { - "label-percent": "", - "label-value": "" + "label-percent": "Persen", + "label-value": "Nilai" }, - "name-labels": "", - "name-legend-values": "", - "name-pie-chart-type": "", + "name-labels": "Label", + "name-legend-values": "Nilai legenda", + "name-pie-chart-type": "Jenis diagram lingkaran", "pie-chart-type-options": { - "label-donut": "", - "label-pie": "" + "label-donut": "Donat", + "label-pie": "Lingkaran" } }, "playlist": { @@ -10169,7 +10684,7 @@ "dependencies": "Dependensi", "documentation": "Dokumentasi", "downloads": "Unduhan", - "from": "", + "from": "Dari", "installedVersion": "Versi Terinstal", "lastCommitDate": "Tanggal melakukan terakhir:", "latestReleaseDate": "Tanggal rilis terbaru:", @@ -10239,7 +10754,7 @@ }, "install-controls-warning": { "body-not-published": "Plugin ini tidak dipublikasikan ke <2>grafana.com/plugins dan tidak dapat dikelola melalui katalog.", - "enterprise-plugin-info": "", + "enterprise-plugin-info": "Plugin ini hanya tersedia di Grafana Cloud dan Grafana Enterprise.", "learn-more": "Pelajari lebih lanjut", "no-valid-grafana-enterprise-license-detected": "Tidak ada lisensi Grafana Enterprise yang valid terdeteksi.", "title-dev-alert": "Ini adalah pengembangan plugin dan instalasinya tidak dapat dihapus.", @@ -10397,28 +10912,50 @@ }, "provisioned-resource-form": { "save-or-delete-resource-shared-fields": { - "comment-placeholder-describe-changes-optional": "", - "description-branch-name-in-git-hub": "", + "comment-placeholder-describe-changes-optional": "Tambahkan catatan untuk menjelaskan perubahan Anda (opsional)", + "description-branch-name-in-git-hub": "Nama cabang di GitHub", "description-inside-repository": "", - "label-branch": "", - "label-comment": "", - "label-path": "", - "label-workflow": "" + "label-branch": "Cabang", + "label-comment": "Komentar", + "label-path": "Jalur", + "label-workflow": "Alur kerja" } }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Fitur ini saat ini sedang dalam pengembangan aktif. Untuk pengalaman terbaik dan peningkatan terbaru, kami merekomendasikan Anda untuk menggunakan <2>versi nightly Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Tambahkan nama yang jelas untuk koneksi repositori ini", "empty": "Kosong", @@ -10428,7 +10965,9 @@ "label-display-name": "Nama tampilan", "placeholder-my-repository-connection": "Koneksi repositori saya", "text-loading-resource-information": "Memuat informasi sumber daya...", - "files-count_other": "{{count}} file" + "dashboards-count_other": "", + "files-count_other": "{{count}} file", + "folders-count_other": "" }, "check-repository": { "check": "Periksa" @@ -10439,36 +10978,20 @@ "button-save": "Simpan", "button-saving": "Menyimpan...", "description-enabled": "Setelah pulling otomatis diaktifkan, target tidak dapat diubah.", - "description-path": "Jalur ke subdirektori di repositori Git", "description-read-only": "Sumber daya tidak dapat dimodifikasi melalui Grafana.", - "description-repository-url": "Masukkan URL repositori GitHub", "description-title": "Nama yang dapat dibaca manusia untuk konfigurasi", "error-required": "Bidang ini wajib diisi.", "error-save-repository": "Gagal menyimpan pengaturan repositori", - "error-valid-github-url": "Masukkan URL repositori GitHub yang valid", "label-automatic-pulling": "Pulling otomatis", - "label-branch": "Cabang", "label-enabled": "Aktif", - "label-github-token": "Token GitHub", "label-interval-seconds": "Interval (detik)", - "label-local-path": "Jalur lokal", - "label-path": "Jalur", - "label-pr-workflow": "Aktifkan opsi permintaan pull saat menyimpan", "label-repository-type": "Jenis repositori", - "label-repository-url": "URL repositori", "label-target": "Target", "label-title": "Judul", "option-entire-instance": "Seluruh instans", - "option-github": "GitHub", - "option-local": "Lokal", "option-managed-folder": "Folder terkelola", - "placeholder-branch": "utama", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Konfigurasi saya", - "placeholder-select-repository-type": "Pilih jenis repositori" + "placeholder-my-config": "Konfigurasi saya" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Konfigurasikan webhook untuk segera mendapatkan pembaruan instan di Grafana setelah perubahan dilakukan. Tinjau dan setujui perubahan menggunakan permintaan pull sebelum live.", @@ -10482,27 +11005,8 @@ }, "connect-repository-button": { "configure": "Konfigurasikan", - "configure-file": "Konfigurasikan penyediaan file", - "configure-git-sync": "Konfigurasikan Sinkronisasi Git", "repository-limit-info-alert": "Batas repositori tercapai ({{count}})" }, - "connect-step": { - "description-branch": "Cabang yang akan digunakan untuk repositori GitHub", - "description-github-path": "Ini adalah jalur ke subdirektori di repositori GitHub Anda tempat dasbor akan disimpan dan disediakan", - "description-paste-your-git-hub-personal-access-token": "Tempel token akses pribadi GitHub Anda", - "description-repository-url": "Tempel URL repositori GitHub Anda", - "error-field-required": "Bidang ini wajib diisi.", - "error-invalid-github-url": "Masukkan URL repositori GitHub yang valid", - "label-access-token": "Token akses GitHub", - "label-branch": "Nama cabang", - "label-local-path": "Jalur lokal", - "label-path": "Jalur ke subdirektori di repositori", - "label-repository-url": "URL repositori GitHub", - "placeholder-branch": "utama", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Hapus", "confirm-delete-repository": "Anda yakin ingin menghapus konfigurasi repositori?", @@ -10534,17 +11038,17 @@ "title-instant-updates-requests-webhooks": "Pembaruan instan dan permintaan pull dengan webhook.", "title-visual-previews-in-pull-requests": "Pratinjau visual dalam permintaan pull dengan rendering gambar" }, + "error-title-default": "", "expanded-row": { "job-specification": "Spesifikasi Pekerjaan", - "summary": "Ringkasan", - "title-error": "Kesalahan" + "summary": "Ringkasan" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Atur tombol fitur yang diwajibkan" }, - "manage-dashboards-provision-updates-automatically": "Kelola dasbor sebagai kode di GitHub dan sediakan pembaruan secara otomatis", - "manage-your-dashboards-with-remote-provisioning": "Mulai menggunakan GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Simpan dasbor di penyimpanan yang dikontrol versi untuk organisasi dan pelacakan riwayat yang lebih baik" }, "file-history-page": { @@ -10565,18 +11069,16 @@ }, "finish-step": { "description-enable-previews": "Menambahkan pratinjau gambar dari perubahan dasbor di permintaan pull. Gambar dasbor Grafana Anda akan dibagikan di repositori Git Anda dan dapat dilihat oleh siapa saja yang memiliki akses repositori.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Membutuhkan rendering gambar. <2>Atur rendering gambar", - "description-often-shall-instance-updates-git-hub": "Seberapa sering instans melakukan pull pada pembaruan dari GitHub?", - "description-pr-enable-description": "Memungkinkan pengguna untuk memilih apakah akan membuka permintaan pull saat menyimpan perubahan. Jika repositori tidak mengizinkan perubahan langsung ke cabang utama, permintaan pull mungkin tetap diperlukan.", + "description-preview-requirements": "", "description-read-only": "Sumber daya tidak dapat dimodifikasi melalui Grafana.", - "description-webhooks-enable": "Memungkinkan pengguna untuk memilih apakah akan membuka permintaan pull saat menyimpan perubahan. Jika repositori tidak mengizinkan perubahan langsung ke cabang utama, permintaan pull mungkin tetap diperlukan.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Aktifkan pratinjau dasbor dalam permintaan pull", - "label-pr-workflow": "Aktifkan opsi permintaan pull saat menyimpan", + "label-generate-dashboard-previews": "", "label-read-only": "Hanya baca", - "label-update-instance-interval-seconds": "Perbarui interval instans (detik)", - "placeholder": "60", - "text-setup-later": "Anda selalu bisa mengatur ini nanti", - "title-enhance-github": "Tingkatkan pengalaman GitHub Anda" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Tidak ada hasil yang cocok dengan kueri Anda", @@ -10607,6 +11109,57 @@ "header": "Penyediaan", "subtitle-provisioning-feature": "Lihat dan kelola koneksi penyediaan Anda" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Tidak ditemukan" }, @@ -10633,9 +11186,17 @@ "starting": "Memulai...", "status": { "title-error-running-job": "Kesalahan saat menjalankan pekerjaan", - "title-job-completed-successfully": "Pekerjaan berhasil diselesaikan" + "title-warning-running-job": "" }, - "summary": "Ringkasan" + "summary": "Ringkasan", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10661,7 +11222,7 @@ }, "repository-actions": { "settings": "Pengaturan", - "source-code": "Kode Sumber" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10726,6 +11287,24 @@ "title-legacy-storage": "Penyimpanan Lama", "title-queued-for-deletion": "Diantrekan untuk dihapus" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Dasar", "dashboard-preview": "Pratinjau Dasbor", @@ -10755,6 +11334,11 @@ "label-current-step": "Langkah saat ini", "label-pending-step": "Langkah tertunda" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Edit konfigurasi", "button-edit": "Edit", @@ -10765,9 +11349,6 @@ "tooltip-unhealthy-repository": "Tidak dapat menerapkan pull pada repositori yang tidak sehat" }, "synchronize-step": { - "error-no-job-id": "Gagal memulai pekerjaan", - "error-no-repository-name": "Tidak ada nama repositori yang disediakan", - "error-starting-job": "Kesalahan saat memulai pekerjaan", "synchronization-description": "Sertakan commit untuk setiap nilai historis", "synchronization-options": "Opsi sinkronisasi" }, @@ -10776,6 +11357,7 @@ "go-to": "Buka", "make-sure": "Pastikan Anda menyertakan izin ini" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Sumber daya tidak akan dapat dibuat, diedit, atau dihapus selama proses ini. Pada langkah terakhir, sumber daya akan menghilang.", "alert-point-2": "Setelah penyediaan selesai, sumber daya akan muncul kembali dan dikelola melalui penyimpanan eksternal.", @@ -11677,112 +12259,112 @@ }, "stat": { "add-orientation-option": { - "description-orientation": "", - "name-orientation": "", + "description-orientation": "Orientasi tata letak", + "name-orientation": "Orientasi", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-vertical": "" + "label-auto": "Otomatis", + "label-horizontal": "Horizontal", + "label-vertical": "Vertikal" } }, "add-standard-data-reduce-options": { - "category-value-options": "", - "description-calculation": "", - "description-fields": "", - "description-limit": "", - "description-show": "", + "category-value-options": "Opsi nilai", + "description-calculation": "Pilih fungsi/perhitungan reducer", + "description-fields": "Pilih bidang yang harus disertakan dalam panel", + "description-limit": "Jumlah baris maksimum untuk ditampilkan", + "description-show": "Hitung satu nilai per kolom atau seri atau tampilkan setiap baris", "fields-options": { - "label-all-fields": "", - "label-numeric-fields": "" + "label-all-fields": "Semua Bidang", + "label-numeric-fields": "Bidang Numerik" }, - "name-calculation": "", - "name-fields": "", - "name-limit": "", - "name-show": "", + "name-calculation": "Perhitungan", + "name-fields": "Bidang", + "name-limit": "Batas", + "name-show": "Tampilkan", "show-options": { - "label-all-values": "", - "label-calculate": "" + "label-all-values": "Semua prinsip", + "label-calculate": "Kalkulasi" } }, - "category-stat-styles": "", + "category-stat-styles": "Gaya statistik", "color-mode-options": { - "label-background-gradient": "", - "label-background-solid": "", - "label-none": "", - "label-value": "" + "label-background-gradient": "Gradien Latar Belakang", + "label-background-solid": "Latar Belakang Solid", + "label-none": "Tidak ada", + "label-value": "Nilai" }, - "description-graph-mode": "", - "description-text-mode": "", + "description-graph-mode": "Grafik panel statistik/mode sparkline", + "description-text-mode": "Kontrol jika nama dan nilai ditampilkan atau hanya nama", "graph-mode": { "options": { - "label-area": "", - "label-none": "" + "label-area": "Area", + "label-none": "Tidak ada" } }, "name-color-modcolor-mode-options": { - "label": "" + "label": "Mode warna" }, - "name-graph-mode": "", - "name-show-percent-change": "", - "name-text-alignment": "", - "name-text-mode": "", - "name-wide-layout": "", - "percent-change-color-mode": "", + "name-graph-mode": "Mode diagram", + "name-show-percent-change": "Tampilkan perubahan persen", + "name-text-alignment": "Perataan teks", + "name-text-mode": "Mode teks", + "name-wide-layout": "Tata letak lebar", + "percent-change-color-mode": "Mode warna perubahan persen", "percent-change-color-mode-options": { - "label-inverted": "", - "label-same-as-value": "", - "label-standard": "" + "label-inverted": "Kebalikan", + "label-same-as-value": "Sama dengan Nilai", + "label-standard": "Standar" }, "text-alignment-options": { - "label-auto": "", - "label-center": "" + "label-auto": "Otomatis", + "label-center": "Pusat" }, "text-mode-options": { - "label-auto": "", - "label-name": "", - "label-none": "", - "label-value": "", - "label-value-and-name": "" + "label-auto": "Otomatis", + "label-name": "Nama", + "label-none": "Tidak ada", + "label-value": "Nilai", + "label-value-and-name": "Nilai dan nama" }, "wide-layout-options": { - "label-off": "", - "label-on": "" + "label-off": "Nonaktif", + "label-on": "Aktif" } }, "state-timeline": { "align-values-options": { - "label-center": "", - "label-left": "", - "label-right": "" + "label-center": "Pusat", + "label-left": "Kiri", + "label-right": "Kanan" }, - "category-state-timeline": "", - "name-align-values": "", - "name-connect-null-values": "", - "name-disconnect-values": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-merge-equal-consecutive-values": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-state-timeline": "Lini masa status", + "name-align-values": "Ratakan nilai", + "name-connect-null-values": "Sambungkan nilai nol", + "name-disconnect-values": "Putuskan sambungan nilai", + "name-fill-opacity": "Opasitas isian", + "name-line-width": "Lebar garis", + "name-merge-equal-consecutive-values": "Gabungkan nilai berurutan yang sama", + "name-page-size": "Ukuran halaman (aktifkan paginasi)", + "name-row-height": "Tinggi baris", + "name-show-values": "Tampilkan nilai", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Selalu", + "label-auto": "Otomatis", + "label-never": "Jangan pernah" } }, "status-history": { - "category-status-history": "", - "name-column-width": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-status-history": "Riwayat status", + "name-column-width": "Lebar kolom", + "name-fill-opacity": "Opasitas isian", + "name-line-width": "Lebar garis", + "name-page-size": "Ukuran halaman (aktifkan paginasi)", + "name-row-height": "Tinggi baris", + "name-show-values": "Tampilkan nilai", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "Selalu", + "label-auto": "Otomatis", + "label-never": "Jangan pernah" }, "status-history-panel": { "too-many-points_other": "Terlalu banyak poin untuk divisualisasikan dengan benar. <1>Perbarui kueri untuk menghasilkan lebih sedikit poin. <3>({{count}} poin diterima)" @@ -11821,13 +12403,13 @@ "label-gauge-display-mode": "Mode tampilan alat ukur", "label-value-display": "Tampilan nilai" }, - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Opsi sel", + "category-table": "Tabel", + "category-table-footer": "Footer tabel", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Besar", + "label-medium": "Sedang", + "label-small": "Kecil" }, "color-background-cell-options-editor": { "description-apply-to-entire-row": "Jika dipilih, seluruh baris akan berwarna seperti sel ini.", @@ -11837,26 +12419,25 @@ }, "label-apply-to-entire-row": "Terapkan ke seluruh baris", "label-background-display-mode": "Mode tampilan latar belakang", - "label-wrap-text": "Pecah baris teks", "wrap-text": "Pecah baris teks" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Otomatis", + "label-center": "Pusat", + "label-left": "Kiri", + "label-right": "Kanan" }, "container": { "content": "Menampilkan terlalu banyak kolom dalam satu tabel dapat memengaruhi kinerja dan membuat data lebih sulit dibaca. Pertimbangkan untuk menyempurnakan kueri Anda.", "show-all-series": "Tampilkan semua kolom", "show-only-series": "Menampilkan {{MAX_NUMBER_OF_COLUMNS}} kolom saja" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", + "description-calculation": "Pilih fungsi/perhitungan reducer", + "description-cell-value-inspect": "Aktifkan pemeriksaan nilai sel di jendela modal", + "description-column-filter": "Mengaktifkan/menonaktifkan filter bidang di tabel", + "description-count-rows": "Tampilkan satu hitungan untuk semua baris data", + "description-fields": "Pilih bidang yang harus dihitung", + "description-min-column-width": "Lebar minimum untuk perubahan ukuran kolom otomatis", "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "Teks alternatif yang akan ditampilkan jika gambar tidak dapat ditampilkan atau untuk pengguna yang menggunakan pembaca layar", @@ -11864,61 +12445,61 @@ "label-alt-text": "Teks alt", "label-title-text": "Teks judul" }, - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-paginations": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "name-wrap-header-text": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "name-calculation": "Perhitungan", + "name-cell-height": "Tinggi sel", + "name-cell-type": "Jenis sel", + "name-cell-value-inspect": "Pemeriksaan nilai sel", + "name-column-alignment": "Perataan kolom", + "name-column-filter": "Filter kolom", + "name-column-width": "Lebar kolom", + "name-count-rows": "Hitung baris", + "name-enable-paginations": "Aktifkan paginasi", + "name-fields": "Bidang", + "name-hide-in-table": "Sembunyikan di tabel", + "name-min-column-width": "Lebar kolom minimum", + "name-show-table-footer": "Tampilkan footer tabel", + "name-show-table-header": "Tampilkan header tabel", + "name-wrap-header-text": "Bungkus teks header", + "placeholder-column-width": "otomatis", + "placeholder-fields": "Semua Bidang Numerik" }, "table-new": { - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "Opsi sel", + "category-table": "Tabel", + "category-table-footer": "Footer tabel", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "Besar", + "label-medium": "Sedang", + "label-small": "Kecil" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "Otomatis", + "label-center": "Pusat", + "label-left": "Kiri", + "label-right": "Kanan" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-pagination": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "description-calculation": "Pilih fungsi/perhitungan reducer", + "description-cell-value-inspect": "Aktifkan pemeriksaan nilai sel di jendela modal", + "description-column-filter": "Mengaktifkan/menonaktifkan filter bidang di tabel", + "description-count-rows": "Tampilkan satu hitungan untuk semua baris data", + "description-fields": "Pilih bidang yang harus dihitung", + "description-min-column-width": "Lebar minimum untuk perubahan ukuran kolom otomatis", + "name-calculation": "Perhitungan", + "name-cell-height": "Tinggi sel", + "name-cell-type": "Jenis sel", + "name-cell-value-inspect": "Pemeriksaan nilai sel", + "name-column-alignment": "Perataan kolom", + "name-column-filter": "Filter kolom", + "name-column-width": "Lebar kolom", + "name-count-rows": "Hitung baris", + "name-enable-pagination": "Aktifkan paginasi", + "name-fields": "Bidang", + "name-hide-in-table": "Sembunyikan di tabel", + "name-min-column-width": "Lebar kolom minimum", + "name-show-table-footer": "Tampilkan footer tabel", + "name-show-table-header": "Tampilkan header tabel", + "placeholder-column-width": "otomatis", + "placeholder-fields": "Semua Bidang Numerik" }, "tag-filter": { "clear-button": "Hapus tag", @@ -12003,17 +12584,17 @@ } }, "text": { - "category-text": "", + "category-text": "Teks", "mode-options": { - "label-code": "", - "label-html": "", - "label-markdown": "" + "label-code": "Kode", + "label-html": "HTML", + "label-markdown": "Markdown" }, - "name-content": "", - "name-language": "", - "name-mode": "", - "name-show-line-numbers": "", - "name-show-mini-map": "" + "name-content": "Konten", + "name-language": "Bahasa", + "name-mode": "Mode", + "name-show-line-numbers": "Tampilkan nomor baris", + "name-show-mini-map": "Tampilkan peta mini" }, "theme-preview": { "breadcrumbs": { @@ -12061,7 +12642,7 @@ "apply-button": "Gunakan rentang waktu", "default-error": "Masukkan tanggal sebelumnya atau \"{{now}}\"", "fiscal-year": "Tahun fiskal: {{from}} - {{to}}", - "from-input": "", + "from-input": "Dari", "open-input-calendar": "Buka kalender", "range-error": "\"Dari\" tidak boleh setelah \"Hingga\"", "to-input": "Hingga" @@ -12079,7 +12660,7 @@ "aria-role": "Pemilihan rentang waktu", "default-title": "Rentang waktu", "example-title": "Contoh rentang waktu", - "from-label": "", + "from-label": "Dari", "from-to": " {{timeOptionFrom}} hingga{{timeOptionTo}} ", "specify": "Tentukan rentang waktu", "submit-button-label": "Tombol kirim TimePicker", @@ -12092,8 +12673,8 @@ }, "timeline": { "missing-field": { - "all": "", - "time": "" + "all": "Tidak ada bidang yang dapat digambarkan dalam bentuk diagram", + "time": "Data tidak memiliki bidang waktu" } }, "timeseries": { @@ -12111,54 +12692,54 @@ "tooltip-delete": "Hapus", "tooltip-edit": "Edit" }, - "category-axis": "", + "category-axis": "Sumbu", "config": { "get-graph-field-config": { - "aria-label-bar-width-factor": "", - "aria-label-fill-opacity": "", - "aria-label-line-width": "", - "aria-label-point-size": "", - "category-graph-styles": "", - "category-thresholds": "", - "name-bar-alignment": "", - "name-bar-width-factor": "", - "name-connect-nulls": "", - "name-disconnect-values": "", - "name-fill-below-to": "", - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-interpolation": "", - "name-line-style": "", - "name-line-width": "", - "name-point-size": "", - "name-show-points": "", - "name-show-thresholds": "", - "name-style": "", - "name-transform": "", + "aria-label-bar-width-factor": "Faktor lebar batang", + "aria-label-fill-opacity": "Opasitas isian", + "aria-label-line-width": "Lebar garis", + "aria-label-point-size": "Ukuran poin", + "category-graph-styles": "Gaya tampilan diagram", + "category-thresholds": "Ambang batas", + "name-bar-alignment": "Perataan batang", + "name-bar-width-factor": "Faktor lebar batang", + "name-connect-nulls": "Sambungkan nilai nol", + "name-disconnect-values": "Putuskan sambungan nilai", + "name-fill-below-to": "Isi di bawah ini untuk", + "name-fill-opacity": "Opasitas isian", + "name-gradient-mode": "Mode gradien", + "name-line-interpolation": "Interpolasi garis", + "name-line-style": "Gaya tampilan garis", + "name-line-width": "Lebar garis", + "name-point-size": "Ukuran poin", + "name-show-points": "Tampilkan poin", + "name-show-thresholds": "Tampilkan ambang batas", + "name-style": "Gaya tampilan", + "name-transform": "Ubah", "transform-options": { - "description-constant": "", - "description-style": "", - "label-constant": "", - "label-style": "" + "description-constant": "Nilai pertama akan ditampilkan sebagai garis konstan", + "description-style": "Balik hasil ke nilai negatif pada sumbu y", + "label-constant": "Konstan", + "label-style": "Y Negatif" } } }, "insert-nulls-editor": { "disconnect-options": { - "label-never": "", - "label-threshold": "" + "label-never": "Jangan pernah", + "label-threshold": "Ambang batas" } }, "line-style-editor": { "line-fill-options": { - "label-dash": "", - "label-dots": "", - "label-solid": "" + "label-dash": "Tanda pisah", + "label-dots": "Titik", + "label-solid": "Solid" }, "title-the-input-expects-a-segment-list": "Input mengharapkan daftar segmen", "tooltip-help": "Bantuan" }, - "name-time-zone": "", + "name-time-zone": "Zona waktu", "nulls-threshold-input": { "placeholder-never": "Jangan pernah" }, @@ -12168,9 +12749,9 @@ }, "span-nulls-editor": { "gaps-options": { - "label-always": "", - "label-never": "", - "label-threshold": "" + "label-always": "Selalu", + "label-never": "Jangan pernah", + "label-threshold": "Ambang batas" } }, "timezones-editor": { @@ -12179,17 +12760,17 @@ } }, "traces": { - "category-span-filters": "", - "name-critical-path-only": "", - "name-find-in-trace": "", - "name-max-duration": "", - "name-min-duration": "", - "name-service-name": "", - "name-service-name-operator": "", - "name-show-matches-only": "", - "name-span-name": "", - "name-span-name-operator": "", - "name-tags": "", + "category-span-filters": "Filter rentang", + "name-critical-path-only": "Hanya tampilkan jalur kritis", + "name-find-in-trace": "Temukan dalam jejak", + "name-max-duration": "Durasi maks", + "name-min-duration": "Durasi min", + "name-service-name": "Nama layanan", + "name-service-name-operator": "Operator nama layanan", + "name-show-matches-only": "Hanya tampilkan kecocokan", + "name-span-name": "Nama span", + "name-span-name-operator": "Operator nama span", + "name-tags": "Tag", "traces-panel": { "no-data-found-in-response": "Tidak ada data yang ditemukan dalam respons" } @@ -12204,20 +12785,20 @@ "axis-editor": { "log-mode-options": { "description": { - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-size": "Pisahkan bucket berdasarkan ukuran" }, "label": { - "split": "" + "split": "Pisah" } }, "mode-options": { "description": { - "split-the-buckets-based-on-count": "", - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-count": "Pisahkan bucket berdasarkan jumlah", + "split-the-buckets-based-on-size": "Pisahkan bucket berdasarkan ukuran" }, "label": { - "count": "", - "size": "" + "count": "Jumlah", + "size": "Ukuran" } }, "placeholder-auto": "Auto" @@ -12249,9 +12830,9 @@ }, "calculate-heatmap": { "add-heatmap-calculation-options": { - "name-x-bucket": "", - "name-y-bucket": "", - "name-y-bucket-scale": "" + "name-x-bucket": "Bucket X", + "name-y-bucket": "Bucket Y", + "name-y-bucket-scale": "Skala Bucket Y" } }, "concatenate-transformer-editor": { @@ -12312,8 +12893,8 @@ "tooltip-enable": "Aktifkan" }, "draggable-sort-order": { - "asc": "", - "desc": "", + "asc": "ASC", + "desc": "DESC", "off": "Nonaktif" }, "editors": { @@ -12712,9 +13293,9 @@ "description": { "reorder-hide-or-rename-fields": "" }, - "field-order": "", + "field-order": "Urutan bidang", "field-order-auto": "Auto", - "field-order-manual": "", + "field-order-manual": "Manual", "first-frame-warning": "Penataan bidang hanya berfungsi dengan satu frame. Pertimbangkan untuk menerapkan transformasi gabungan atau memfilter input terlebih dahulu.", "name": { "organize-fields": "" @@ -12841,7 +13422,7 @@ "polynomial": "Polinomial" } }, - "regression": "", + "regression": "regresi", "tooltip-number-of-xy-points-to-predict": "Jumlah poin X,Y untuk diprediksi" }, "rename-by-regex-transformer": { @@ -12994,10 +13575,10 @@ } }, "trend": { - "category-x-axis": "", - "description-x-field": "", - "name-x-field": "", - "placeholder-x-field": "" + "category-x-axis": "Sumbu X", + "description-x-field": "Nilai numerik yang meningkat", + "name-x-field": "Bidang X", + "placeholder-x-field": "Nilai numerik pertama" }, "upgrade-box": { "discovery-text": "Anda telah menemukan fitur Pro!", @@ -13260,20 +13841,20 @@ } }, "xychart": { - "category-xychart": "", - "name-fill-opacity": "", - "name-line-style": "", - "name-line-width": "", - "name-max-point-size": "", - "name-min-point-size": "", - "name-point-shape": "", - "name-point-size": "", - "name-point-stroke-width": "", - "name-series-mapping": "", - "name-show": "", + "category-xychart": "Diagram XY", + "name-fill-opacity": "Opasitas isian", + "name-line-style": "Gaya tampilan garis", + "name-line-width": "Lebar garis", + "name-max-point-size": "Ukuran poin maks", + "name-min-point-size": "Ukuran poin min", + "name-point-shape": "Bentuk poin", + "name-point-size": "Ukuran poin", + "name-point-stroke-width": "Lebar goresan poin", + "name-series-mapping": "Pemetaan data seri", + "name-show": "Tampilkan", "point-shape-options": { - "label-circle": "", - "label-square": "" + "label-circle": "Lingkaran", + "label-square": "Persegi" }, "series-editor": { "add-series": "Tambahkan data seri", @@ -13285,18 +13866,18 @@ "label-y-field": "Bidang Y", "placeholder-all-frames": "Semua frame", "placeholder-select-frame": "Pilih bingkai", - "placeholder-x-field": "", - "placeholder-y-field": "", + "placeholder-x-field": "Bidang angka atau waktu pertama di setiap bingkai", + "placeholder-y-field": "Bidang angka yang tersisa di setiap bingkai", "tooltip-delete-series": "Hapus data seri" }, "series-mapping-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "Otomatis", + "label-manual": "Manual" }, "show-options": { - "label-both": "", - "label-lines": "", - "label-points": "" + "label-both": "Keduanya", + "label-lines": "Garis", + "label-points": "Poin" } } } \ No newline at end of file diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 8280132fbbc..bea973dbed5 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Modifica il gruppo di valutazione" }, "group-loader": { - "group-load-failed": "Impossibile caricare le regole dal gruppo {{ groupName }} in {{ namespaceName }}" + "group-load-failed": "Impossibile caricare le regole dal gruppo {{ groupName }} in {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Il gruppo è in fase di eliminazione" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configura", - "draft-new-rule": "Scrivi una nuova regola", "ds-error": { "title": "" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nuova regola di avviso", "new-datasource-recording-rule": "Nuova regola di registrazione dell'origine dei dati", "new-grafana-recording-rule": "Nuova regola di registrazione Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Mostra altro…" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Aggiorna anteprima", + "empty-state": "", "label-preview": "Anteprima", "refresh": "Aggiorna" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Nome attributo token ID", "login-attribute-path-description": "Espressione JMESPath da utilizzare per la ricerca dell'accesso utente dal token ID utente.", "login-attribute-path-label": "Percorso attributo di accesso", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "L'ID client dell'identità gestita della credenziale di identità federata della tua app OAuth2.", "managed-identity-client-id-label": "ID client identità gestita FIC", "name-attribute-path-description": "Espressione JMESPath da utilizzare per la ricerca del nome utente dal token ID utente. \nQuesto nome verrà visualizzato come nome dell'utente.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Abilitato", "text-badge-not-enabled": "Non abilitato" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "L'endpoint .well-known/openid-configuration per il tuo IdP", "title-open-id-connect-discovery-url": "URL di OpenID Connect Discovery" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Annulla", "cannot-move-folders": "Non è possibile spostare le cartelle", "confirmation-text": "Elimina", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Questa cartella è vuota" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} regola di avviso", "alertRule_other": "{{count}} regole di avviso", @@ -3554,6 +3582,7 @@ "empty-message": "Nessuna cartella trovata", "error-title": "Errore durante il caricamento delle cartelle", "non-folder-item": "Non cartella {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Cerca cartelle", "unknown-error": "Errore sconosciuto" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "", "panel-description": "", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "element": "", "local-variable": "Variabile locale", "multiple-elements": "Elementi multipli", "multiple-elements-delete-text": "Desideri davvero eliminare questi elementi?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Qualcun altro ha aggiornato questa dashboard", "would-still-dashboard": "Desideri comunque salvare questa dashboard?" }, - "schema-v2-library-panels-export": "La funzionalità della dashboard dinamica è in fase sperimentale e non ha una parità di funzionalità completa con il comportamento attuale delle dashboard. Si basa su un nuovo formato di schema, che non supporta i pannelli della libreria. Ciò significa che quando si esporta la dashboard per utilizzarla in un'altra istanza, non includeremo i pannelli della libreria. Intendiamo supportarli man mano che progrediamo nel <2>ciclo di vita della funzionalità.", - "schema-v2-library-panels-export-title": "Schema Dashboard V2 non supporta ancora l'esportazione di pannelli della libreria da utilizzare in un'altra istanza", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "" } }, + "share-span": "", "warnings": "Allarmi" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Mese precedente", "select-placeholder": "Selezionare data e ora" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Chiudi" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Nessun valore", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} di {{numRows}} righe" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} di {{numRows}} righe", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tag" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Copia la riga del registro", "hide-details": "Mostra i dettagli del registro", "icon-label": "Menu registro", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Fissa registro", "show-context": "Mostra contesto", "show-details": "Nascondi i dettagli del registro", @@ -9682,7 +10196,9 @@ "aria-label": "Guida" }, "invite-user": { - "invite-new-member-button": "Invita un nuovo membro" + "invite-button": "Invito", + "invite-new-member-button": "Invita un nuovo membro", + "invite-tooltip": "" }, "item": { "add-bookmark": "Aggiungi a Segnalibri", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Questa funzione è attualmente in fase di sviluppo attivo. Per un'esperienza ottimale e gli ultimi miglioramenti, consigliamo di utilizzare la <2>nightly build di Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Aggiungi un nome semplice per questa connessione al repository", "empty": "Vuoto", @@ -10460,8 +10998,12 @@ "label-display-name": "Nome visualizzato", "placeholder-my-repository-connection": "La mia connessione al repository", "text-loading-resource-information": "Caricamento delle informazioni sulle risorse in corso...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} file", - "files-count_other": "{{count}} file" + "files-count_other": "{{count}} file", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Verifica" @@ -10472,36 +11014,20 @@ "button-save": "Salva", "button-saving": "Salvataggio in corso...", "description-enabled": "Una volta abilitato il pull automatico, la destinazione non può essere modificata.", - "description-path": "Percorso a una directory secondaria nel repository Git", "description-read-only": "Le risorse non possono essere modificate tramite Grafana.", - "description-repository-url": "Inserisci l'URL del repository GitHub", "description-title": "Un nome comprensibile per la configurazione", "error-required": "Questo campo è obbligatorio.", "error-save-repository": "Impossibile salvare le impostazioni del repository", - "error-valid-github-url": "Inserisci un URL di repository GitHub valido", "label-automatic-pulling": "Pull automatico", - "label-branch": "Ramo", "label-enabled": "Abilitato", - "label-github-token": "Token GitHub", "label-interval-seconds": "Intervallo (secondi)", - "label-local-path": "Percorso locale", - "label-path": "Percorso", - "label-pr-workflow": "Abilita l'opzione di richiesta di pull durante il salvataggio", "label-repository-type": "Tipo di repository", - "label-repository-url": "URL repository", "label-target": "Destinazione", "label-title": "Titolo", "option-entire-instance": "Intera istanza", - "option-github": "GitHub", - "option-local": "Locale", "option-managed-folder": "Cartella gestita", - "placeholder-branch": "principale", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "La mia configurazione", - "placeholder-select-repository-type": "Seleziona tipo di repository" + "placeholder-my-config": "La mia configurazione" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configura i webhook per ottenere aggiornamenti istantanei in Grafana non appena vengono apportate le modifiche. Esamina e approva le modifiche utilizzando le richieste di pull prima che vengano pubblicate.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configura", - "configure-file": "Configura il provisioning dei file", - "configure-git-sync": "Configura sincronizzazione Git", "repository-limit-info-alert": "Limite del repository raggiunto ({{count}})" }, - "connect-step": { - "description-branch": "Ramo da utilizzare per il repository GitHub", - "description-github-path": "Questo è il percorso a una directory secondaria nel repository GitHub in cui le dashboard verranno archiviate e fornite da", - "description-paste-your-git-hub-personal-access-token": "Incolla il tuo token di accesso personale GitHub", - "description-repository-url": "Incolla l'URL del tuo repository GitHub", - "error-field-required": "Questo campo è obbligatorio.", - "error-invalid-github-url": "Inserisci un URL di repository GitHub valido", - "label-access-token": "Token di accesso GitHub", - "label-branch": "Nome del ramo", - "label-local-path": "Percorso locale", - "label-path": "Percorso alla directory secondaria nel repository", - "label-repository-url": "URL del repository GitHub", - "placeholder-branch": "principale", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Elimina", "confirm-delete-repository": "Desideri davvero eliminare la configurazione del repository?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Aggiornamenti istantanei e richieste di pull con webhook.", "title-visual-previews-in-pull-requests": "Anteprime visive nelle richieste di pull con rendering delle immagini" }, + "error-title-default": "", "expanded-row": { "job-specification": "Specifiche dell'attività", - "summary": "Indice", - "title-error": "Errore" + "summary": "Indice" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Configura i toggle delle funzionalità necessarie" }, - "manage-dashboards-provision-updates-automatically": "Gestisci le dashboard come codice in GitHub e fornisci automaticamente gli aggiornamenti", - "manage-your-dashboards-with-remote-provisioning": "Inizia con GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Archivia le dashboard in una memoria controllata dalla versione per una migliore organizzazione e monitoraggio della cronologia" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Aggiunge un'anteprima dell'immagine delle modifiche alla dashboard nelle richieste di pull. Le immagini delle dashboard Grafana verranno condivise nel repository Git e saranno visibili a chiunque abbia accesso al repository.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Richiede il rendering dell'immagine. <2>Configura il rendering delle immagini", - "description-often-shall-instance-updates-git-hub": "Con quale frequenza l'istanza deve eseguire un pull degli aggiornamenti da GitHub?", - "description-pr-enable-description": "Consente agli utenti di scegliere se aprire una richiesta di pull durante il salvataggio delle modifiche. Se il repository non consente modifiche dirette al ramo principale, potrebbe essere comunque necessaria una richiesta di pull.", + "description-preview-requirements": "", "description-read-only": "Le risorse non possono essere modificate tramite Grafana.", - "description-webhooks-enable": "Consente agli utenti di scegliere se aprire una richiesta di pull durante il salvataggio delle modifiche. Se il repository non consente modifiche dirette al ramo principale, potrebbe essere comunque necessaria una richiesta di pull.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Abilita le anteprime della dashboard nelle richieste di pull", - "label-pr-workflow": "Abilita l'opzione di richiesta di pull durante il salvataggio", + "label-generate-dashboard-previews": "", "label-read-only": "Solo lettura", - "label-update-instance-interval-seconds": "Aggiorna intervallo istanza (secondi)", - "placeholder": "60", - "text-setup-later": "È sempre possibile configurarlo in un secondo momento", - "title-enhance-github": "Migliora la tua esperienza GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Nessun risultato corrisponde alla tua query", @@ -10640,6 +11145,57 @@ "header": "Provisioning", "subtitle-provisioning-feature": "Visualizza e gestisci le connessioni di provisioning" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Non trovato" }, @@ -10666,9 +11222,17 @@ "starting": "Avvio in corso...", "status": { "title-error-running-job": "Errore durante l'esecuzione dell'attività", - "title-job-completed-successfully": "Attività completata con successo" + "title-warning-running-job": "" }, - "summary": "Indice" + "summary": "Indice", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Impostazioni", - "source-code": "Codice sorgente" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Memoria esistente", "title-queued-for-deletion": "In coda per l'eliminazione" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Base", "dashboard-preview": "Anteprima dashboard", @@ -10788,6 +11370,11 @@ "label-current-step": "Passaggio corrente", "label-pending-step": "Passaggio in sospeso" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Modifica configurazione", "button-edit": "Modifica", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Impossibile eseguire il pull di un repository non integro" }, "synchronize-step": { - "error-no-job-id": "Impossibile avviare l'attività", - "error-no-repository-name": "Nessun nome di repository fornito", - "error-starting-job": "Errore durante l'avvio dell'attività", "synchronization-description": "Includi commit per ogni valore storico", "synchronization-options": "Opzioni di sincronizzazione" }, @@ -10809,6 +11393,7 @@ "go-to": "Vai a", "make-sure": "Assicurati di includere queste autorizzazioni" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Le risorse non potranno essere create, modificate o eliminate durante questo processo. Nell'ultimo passaggio, scompariranno.", "alert-point-2": "Una volta completato il provisioning, le risorse riappariranno e verranno gestite tramite la memoria esterna.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "", "label-background-display-mode": "", - "label-wrap-text": "", "wrap-text": "" }, "column-alignment-options": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 77fd1785e31..111f3266e28 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -119,10 +119,10 @@ "users-heading": "組織ユーザー" }, "enterprise-auth-features-card": { - "dismiss": "", - "heading": "", - "learn-more-link": "", - "text": "" + "dismiss": "閉じる", + "heading": "エンタープライズ認証", + "learn-more-link": "詳細を見る", + "text": "<1>SAML、<3>SCIM、<6>LDAP、<8>RBACを使用して、ユーザー、チーム、権限を自動的に管理できます。Grafana CloudとEnterpriseで利用可能です。" }, "feature-listing": { "title-auditing": "監査", @@ -374,8 +374,8 @@ "am-active-timing-select-label-active-timings": "アクティブタイミング" }, "ad": { - "close": "", - "learn-more": "" + "close": "閉じる", + "learn-more": "詳細を見る" }, "add-button": { "add-more": "さらに追加" @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -404,12 +405,12 @@ "missing-series-resolve": "解決されるまでに必要なデータが欠落した評価回数", "no-annotations": "注釈なし", "notification-configuration": { - "active-timings": "", + "active-timings": "アクティブ時間間隔", "contact-point": "コンタクトポイント", "group-by": "次でグループ化:", - "group-title": "", + "group-title": "通知設定", "mute-timings": "ミュート期間", - "timing-options": "" + "timing-options": "タイミング" }, "pending-period": "保留期間", "rule": "ルール", @@ -957,7 +958,7 @@ }, "contactPoints": { "validation": { - "notFound": "" + "notFound": "コンタクトポイント「{{contactPoint}}」が見つかりませんでした" } }, "continue-matching-indicator": { @@ -966,7 +967,7 @@ "copy-to-clipboard": "「{{label}}」をクリップボードにコピー", "create-metadata": { "label": { - "contact-point": "", + "contact-point": "通知の配信先", "dashboard": "ダッシュボード", "dashboard-and-panel": "ダッシュボードとパネル", "evaluation-interval": "評価間隔", @@ -1114,7 +1115,7 @@ "content-show-all-errors": "すべてのエラーを表示" }, "errors": { - "failedWith": "" + "failedWith": "{{status}}により{{-config}}が失敗しました: {{-message}}" }, "evaluate-every-validation-options": { "message": { @@ -1545,7 +1546,8 @@ "title": "評価グループを編集" }, "group-loader": { - "group-load-failed": "{{ namespaceName }}の{{ groupName }}グループからルールを読み込めませんでした" + "group-load-failed": "{{ namespaceName }}の{{ groupName }}グループからルールを読み込めませんでした", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "グループを削除中です" @@ -1562,20 +1564,20 @@ } }, "home": { - "irm-card-description": "", - "irm-card-item-1": "", - "irm-card-item-2": "", - "irm-card-item-3": "", - "irm-card-item-4": "", - "irm-card-title": "", + "irm-card-description": "Grafana Cloud IRMでオンコール、アラート、インシデント対応を統合できます。", + "irm-card-item-1": "カレンダーまたはTerraformでオンコールスケジュールを管理できます。", + "irm-card-item-2": "Web、アプリ、Slack、その他のチャネルからインシデントに対応できます。", + "irm-card-item-3": "AI搭載のGrafana SIFTで根本原因を特定できます。", + "irm-card-item-4": "過去のインシデントを分析して、対応力と回復力を向上させます。", + "irm-card-title": "インシデント対応と管理", "label-get-started": "開始する", "label-insights": "インサイト", - "synthetic-monitoring-card-description": "", - "synthetic-monitoring-card-item-1": "", - "synthetic-monitoring-card-item-2": "", - "synthetic-monitoring-card-item-3": "", - "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", + "synthetic-monitoring-card-description": "世界各地から重要なユーザーフロー、ウェブサイト、APIを外部監視します。", + "synthetic-monitoring-card-item-1": "ブラウザチェックによりユーザージャーニー全体をシミュレートします。", + "synthetic-monitoring-card-item-2": "全ネットワーク層でping、DNS、HTTP/S、TCPチェックを実行します。", + "synthetic-monitoring-card-item-3": "20以上のグローバルプローブまたはファイアウォール内のプライベートプローブを使用します。", + "synthetic-monitoring-card-item-4": "UIから直接、組み込みのPrometheusスタイルアラートでSLOを追跡します。", + "synthetic-monitoring-card-title": "合成モニタリング", "title-alerting": "アラート" }, "import-to-gma": { @@ -1590,7 +1592,7 @@ "no-rules-body-yaml": "インポートできるルールがありません。別のyamlファイルを選択してください。", "no-rules-title": "インポートするルールはありません", "not-using-rules-managed-by-integrations-or-plugins": { - "text": "", + "text": "統合またはプラグインで管理されるルールは、Grafana管理ルールにインポートしないでください。", "title": "情報" }, "plugin-rules-warning": { @@ -1969,7 +1971,7 @@ }, "notification-policies-filter": { "label-search-by-contact-point": "連絡先で検索", - "placeholder-search-by-contact-point": "", + "placeholder-search-by-contact-point": "コンタクトポイントを選択", "search-query-input-placeholder-search": "検索" }, "notification-policies-list": { @@ -2456,7 +2458,6 @@ }, "rule-list": { "configure-datasource": "構成", - "draft-new-rule": "新しいルールの下書きを作成", "ds-error": { "title": "このデータソースのルールを読み込めません" }, @@ -2477,11 +2478,12 @@ "new-badge": "新登場!", "text": "Grafana管理ルールにインポート" }, - "loading-more-groups": "", + "loading-more-groups": "その他のグループを読み込み中...", "more": "その他", "new-alert-rule": "新しいアラートルール", "new-datasource-recording-rule": "新しいデータソース記録ルール", "new-grafana-recording-rule": "新しいGrafana記録ルール", + "new-rule-for-export": "", "pagination": { "next-page": "もっと見る..." }, @@ -2789,6 +2791,7 @@ }, "template-preview": { "aria-label-refresh-preview": "プレビューを更新", + "empty-state": "", "label-preview": "プレビュー", "refresh": "更新" }, @@ -3025,51 +3028,51 @@ } }, "alertlist": { - "category-alert-state-filter": "", - "category-filter": "", - "category-options": "", - "descriptino-alerts-linked-to-dashboard": "", - "description-alert-instance-label": "", - "description-alert-name": "", - "description-datasource": "", - "description-folder": "", - "description-group-by": "", - "description-group-mode": "", - "description-max-items": "", - "description-show-inactive-alerts": "", - "description-sort-order": "", - "description-view-mode": "", + "category-alert-state-filter": "アラート状態フィルター", + "category-filter": "フィルター", + "category-options": "オプション", + "descriptino-alerts-linked-to-dashboard": "このダッシュボードにリンクされているアラートのみを表示", + "description-alert-instance-label": "ラベルクエリでアラートインスタンスをフィルタリング(例: {severity=\"critical\", instance=~\"cluster-us-.+\"})", + "description-alert-name": "このテキストを含むアラートでフィルタリング", + "description-datasource": "アラートソースでフィルタリング", + "description-folder": "選択したフォルダ内のアラートでフィルタリング(Grafana管理アラートルールのみ)", + "description-group-by": "ラベルクエリでアラートをフィルタリング", + "description-group-mode": "アラートインスタンスをグループ化する方法", + "description-max-items": "表示する最大アラート数", + "description-show-inactive-alerts": "インスタンスが0(ゼロ)のアラートルールを含めます。これらのルールにはインスタンスがないため、アラートインスタンスラベルフィルターが設定されている場合は非表示になります。", + "description-sort-order": "アラートとアラートインスタンスの並び順", + "description-view-mode": "リストビューと統計ビューの切り替え", "group-by": { "aria-label-group-by-label-keys": "ラベルキーでグループ化", "placeholder-group-by": "グループ化" }, "group-mode-options": { - "label-custom-grouping": "", - "label-default-grouping": "" + "label-custom-grouping": "カスタムグループ化", + "label-default-grouping": "デフォルトのグループ化" }, - "name-alert-instance-label": "", - "name-alert-name": "", - "name-alerts-linked-to-dashboard": "", - "name-datasource": "", - "name-error": "", - "name-firing": "", - "name-folder": "", - "name-group-by": "", - "name-group-mode": "", - "name-max-items": "", - "name-no-data": "", - "name-normal": "", - "name-pending": "", - "name-recovering": "", - "name-show-inactive-alerts": "", - "name-sort-order": "", - "name-view-mode": "", + "name-alert-instance-label": "アラートインスタンスラベル", + "name-alert-name": "アラート名", + "name-alerts-linked-to-dashboard": "このダッシュボードにリンクされているアラート", + "name-datasource": "データソース", + "name-error": "エラー", + "name-firing": "アラート/発生中", + "name-folder": "フォルダ", + "name-group-by": "グループ化", + "name-group-mode": "グループモード", + "name-max-items": "最大項目数", + "name-no-data": "データなし", + "name-normal": "通常", + "name-pending": "保留中", + "name-recovering": "復旧中", + "name-show-inactive-alerts": "インスタンス数が0のアラートを表示", + "name-sort-order": "並び順", + "name-view-mode": "表示モード", "sort-order-options": { - "label-alphabetical-asc": "", - "label-alphabetical-desc": "", - "label-importance": "", - "label-time-asc": "", - "label-time-desc": "" + "label-alphabetical-asc": "アルファベット順(昇順)", + "label-alphabetical-desc": "アルファベット順(降順)", + "label-importance": "重要度", + "label-time-asc": "時間(昇順)", + "label-time-desc": "時間(降順)" }, "ungrouped-mode-view": { "active-for": "<1>{{duration}}間", @@ -3085,8 +3088,8 @@ "title-permission-required": "権限が必要です" }, "view-mode-options": { - "label-list": "", - "label-stat": "" + "label-list": "リスト", + "label-stat": "統計" } }, "annolist": { @@ -3099,31 +3102,31 @@ "annotation-list-item": { "tooltip-created-by": "作成者:<1> {{email}} " }, - "category-annotation-query": "", - "category-display": "", - "category-link-behaviour": "", - "description-tags": "", + "category-annotation-query": "注釈クエリ", + "category-display": "表示", + "category-link-behaviour": "リンクの動作", + "description-tags": "注釈タグを一致", "link-target-options": { - "label-dashboard": "", - "label-panel": "" + "label-dashboard": "ダッシュボード", + "label-panel": "パネル" }, - "name-limit": "", - "name-link-target": "", - "name-query-filter": "", - "name-show-tags": "", - "name-show-time": "", - "name-show-user": "", - "name-tags": "", - "name-time-after": "", - "name-time-before": "", - "name-time-range": "", + "name-limit": "制限", + "name-link-target": "リンク先", + "name-query-filter": "クエリフィルター", + "name-show-tags": "タグを表示", + "name-show-time": "時間を表示", + "name-show-user": "ユーザーを表示", + "name-tags": "タグ", + "name-time-after": "次の時間より後", + "name-time-before": "次の時間より前", + "name-time-range": "時間範囲", "query-filter-options": { - "label-all-dashboards": "", - "label-this-dashboard": "" + "label-all-dashboards": "すべてのダッシュボード", + "label-this-dashboard": "このダッシュボード" }, "time-range-options": { - "label-none": "", - "label-this-dashboard": "" + "label-none": "なし", + "label-this-dashboard": "このダッシュボード" } }, "annotations": { @@ -3243,6 +3246,11 @@ "id-token-attribute-name-label": "IDトークン属性名", "login-attribute-path-description": "ユーザーIDトークンからのユーザーログイン検索に使用するJMESPath式。", "login-attribute-path-label": "ログイン属性パス", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "OAuth2アプリのフェデレーテッドID資格情報のマネージドIDクライアントID。", "managed-identity-client-id-label": "FICマネージドIDクライアントID", "name-attribute-path-description": "ユーザーIDトークンからのユーザー名検索に使用するJMESPath式。\nこの名前は、ユーザーの表示名として使用されます。", @@ -3313,8 +3321,8 @@ } }, "provider-card": { - "saml-learn-more": "", - "scim-learn-more": "", + "saml-learn-more": "SAMLによるシングルサインオン(SSO)。", + "scim-learn-more": "SCIMでユーザーとチームを同期。", "text-badge-enabled": "有効化", "text-badge-not-enabled": "有効になっていません" }, @@ -3341,6 +3349,9 @@ "text-badge-enabled": "有効化", "text-badge-not-enabled": "有効になっていません" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "IdPの.well-known/openid-configurationエンドポイント", "title-open-id-connect-discovery-url": "OpenID Connect Discovery URL" @@ -3351,99 +3362,99 @@ }, "bar-chart": { "warn": { - "missing-numeric": "", - "missing-series": "" + "missing-numeric": "数値フィールドが見つかりません", + "missing-series": "棒グラフには文字列または時間フィールドが必要です" } }, "barchart": { "config": { - "category-thresholds": "", - "description-color-by-field": "", - "description-x-label-max-length": "", - "name-bar-radius": "", - "name-bar-width": "", - "name-color-by-field": "", - "name-fill-opacity": "", - "name-full-highlight": "", - "name-gradient-mode": "", - "name-group-width": "", - "name-line-width": "", - "name-orientation": "", - "name-rotate-x-labels": "", - "name-show-thresholds": "", - "name-show-values": "", - "name-stacking": "", - "name-transform": "", - "name-x-axis": "", - "name-x-label-max-length": "", - "name-x-label-min-spacing": "", + "category-thresholds": "しきい値", + "description-color-by-field": "隣接フィールドの色の値を使用して各バーの値を色付けします。", + "description-x-label-max-length": "X軸ラベルは指定された長さに切り詰められます", + "name-bar-radius": "バー半径", + "name-bar-width": "バー幅", + "name-color-by-field": "フィールド別の色", + "name-fill-opacity": "塗りつぶし不透明度", + "name-full-highlight": "カーソルを置いたときに全領域をハイライト", + "name-gradient-mode": "グラデーションモード", + "name-group-width": "グループ幅", + "name-line-width": "線の太さ", + "name-orientation": "向き", + "name-rotate-x-labels": "X軸の目盛ラベルを回転", + "name-show-thresholds": "しきい値を表示", + "name-show-values": "値を表示", + "name-stacking": "積み上げ", + "name-transform": "変換", + "name-x-axis": "X軸", + "name-x-label-max-length": "X軸の目盛ラベルの最大長", + "name-x-label-min-spacing": "X軸ラベルの最小間隔", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-line-vertical": "" + "label-auto": "自動", + "label-horizontal": "水平", + "label-line-vertical": "垂直" }, - "placeholder-x-axis": "", - "placeholder-x-label-max-length": "", + "placeholder-x-axis": "最初の文字列または時間フィールド", + "placeholder-x-label-max-length": "なし", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "常時", + "label-auto": "自動", + "label-never": "なし" }, "transform-options": { - "description-constant": "", - "description-negative-y": "", - "label-constant": "", - "label-negative-y": "" + "description-constant": "最初の値は定数線として表示されます", + "description-negative-y": "結果をY軸の負の値に反転表示", + "label-constant": "定数", + "label-negative-y": "Y軸を反転" } }, "tick-spacing-editor": { "content-require-space-from-the-right-side": "右側にスペースが必要", "gaps-options": { - "description-large": "", - "description-medium": "", - "description-none": "", - "description-small": "", - "label-large": "", - "label-medium": "", - "label-none": "", - "label-small": "" + "description-large": "{{spacing}}の間隔が必要", + "description-medium": "{{spacing}}の間隔が必要", + "description-none": "すべての目盛を表示", + "description-small": "{{spacing}}の間隔が必要", + "label-large": "大", + "label-medium": "中", + "label-none": "なし", + "label-small": "小" }, "label-rtl": "RTL" } }, "bargauge": { "bar-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "自動", + "label-manual": "手動" }, - "category-bar-gauge": "", - "description-max-height": "", - "description-min-height": "", - "description-min-width": "", - "description-show-unfilled-area": "", + "category-bar-gauge": "バーゲージ", + "description-max-height": "最大行の高さ(水平方向)", + "description-min-height": "最小行の高さ(水平方向)", + "description-min-width": "最小列幅(垂直方向)", + "description-show-unfilled-area": "有効にすると、未入力の領域がグレーで表示されます", "display-mode-options": { - "label-basic": "", - "label-gradient": "", - "label-retro": "" + "label-basic": "ベーシック", + "label-gradient": "グラデーション", + "label-retro": "レトロLCD" }, - "name-bar-size": "", - "name-display-mode": "", - "name-max-height": "", - "name-min-height": "", - "name-min-width": "", - "name-name-placement": "", + "name-bar-size": "バーサイズ", + "name-display-mode": "表示モード", + "name-max-height": "最大の高さ", + "name-min-height": "最小の高さ", + "name-min-width": "最小幅", + "name-name-placement": "名前の配置", "name-placement-options": { - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-top": "" + "label-auto": "自動", + "label-hidden": "非表示", + "label-left": "左", + "label-top": "上" }, - "name-show-unfilled-area": "", - "name-value-display": "", + "name-show-unfilled-area": "未入力の領域を表示", + "name-value-display": "値を表示", "value-display-options": { - "label-hidden": "", - "label-text-color": "", - "label-value-color": "" + "label-hidden": "非表示", + "label-text-color": "文字色", + "label-value-color": "値の色" } }, "bookmarks-page": { @@ -3458,7 +3469,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "キャンセル", "cannot-move-folders": "フォルダは移動できません", "confirmation-text": "削除", @@ -3468,7 +3478,7 @@ "delete-modal-restore-dashboards-text": "このアクションにより、選択したフォルダはすぐに削除されますが、選択したダッシュボードは30日後に削除されます。組織の管理者は、30日が経過する前であればいつでもダッシュボードを復元できます。フォルダは復元できません。", "delete-modal-text": "この操作により、次のコンテンツが削除されます:", "delete-modal-title": "削除", - "delete-provisioned-folder": "", + "delete-provisioned-folder": "プロビジョニングされたフォルダを削除", "deleting": "削除中…", "manage-permissions-button": "権限を管理する", "move-button": "移動", @@ -3491,6 +3501,23 @@ "browse-view": { "this-folder-is-empty": "このフォルダは空です" }, + "bulk-action-resources-form": { + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_other": "{{count}}件のアラートルール", "dashboard_other": "{{count}}個のダッシュボード", @@ -3507,12 +3534,12 @@ "tags-column": "タグ" }, "delete-provisioned-folder-form": { - "alert-folder-deleted-successfully": "", - "api-error": "", - "button-cancel": "", - "button-delete": "", - "button-deleting": "", - "delete-warning": "" + "alert-folder-deleted-successfully": "フォルダが正常に削除されました", + "api-error": "フォルダの削除に失敗しました", + "button-cancel": "キャンセル", + "button-delete": "削除", + "button-deleting": "削除中…", + "delete-warning": "このフォルダとその下位のすべてのフォルダやコンテンツが削除されます。全部で、次に影響します: " }, "descendant-count": { "title-unable-to-retrieve-descendant-information": "下位情報を取得できません" @@ -3536,6 +3563,7 @@ "empty-message": "フォルダが見つかりません", "error-title": "フォルダの読み込み中にエラーが発生しました", "non-folder-item": "非フォルダ {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "フォルダを検索", "unknown-error": "不明なエラー" }, @@ -3571,52 +3599,52 @@ "text": "クエリに一致する結果が見つかりませんでした。" }, "restore": { - "success": "" + "success": "ダッシュボード{{name}}が復元されました" }, "text-this-repository-is-read-only": "ターゲットに直接アクセスできる場合は、JSONをコピーしてそこに貼り付けます。", "trash-state-manager": { "label": { - "alphabetically-az": "", - "alphabetically-za": "" + "alphabetically-az": "アルファベット順(A-Z)", + "alphabetically-za": "アルファベット順(Z-A)" } } }, "candlestick": { "additional-fields-options": { - "label-ignore": "", - "label-include": "" + "label-ignore": "無視", + "label-include": "含める" }, "candle-style-options": { - "label-candles": "", - "label-ohlc-bars": "" + "label-candles": "ローソク足", + "label-ohlc-bars": "OHLCバー" }, - "category-candlestick": "", + "category-candlestick": "ローソク足", "color-strategy-options": { - "label-since-open": "", - "label-since-prior-close": "" + "label-since-open": "始値から", + "label-since-prior-close": "前回終値から" }, - "description-additional-fields": "", - "description-close": "", - "description-high": "", - "description-low": "", - "description-open": "", - "description-volume": "", + "description-additional-fields": "標準の時系列オプションを使用して、上記でマッピングされていないフィールドを設定します", + "description-close": "期間終了時の値", + "description-high": "期間内の最大値", + "description-low": "期間内の最小値", + "description-open": "期間開始時の値", + "description-volume": "期間内のサンプル数", "mode-options": { - "label-both": "", - "label-candles": "", - "label-volume": "" + "label-both": "両方", + "label-candles": "ローソク足", + "label-volume": "数量" }, - "name-additional-fields": "", - "name-candle-style": "", - "name-close": "", - "name-color-strategy": "", - "name-down-color": "", - "name-high": "", - "name-low": "", - "name-mode": "", - "name-open": "", - "name-up-color": "", - "name-volume": "" + "name-additional-fields": "追加フィールド", + "name-candle-style": "ローソク足スタイル", + "name-close": "終値", + "name-color-strategy": "カラー方式", + "name-down-color": "下落色", + "name-high": "高値", + "name-low": "安値", + "name-mode": "モード", + "name-open": "始値", + "name-up-color": "上昇色", + "name-volume": "数量" }, "canvas": { "apieditor": { @@ -3631,18 +3659,18 @@ } }, "button-item": { - "category-button": "", + "category-button": "ボタン", "label": { "center": "中央", "left": "左", "right": "右" }, - "name-align-text": "", - "name-api": "", - "name-style": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", + "name-align-text": "テキストの配置", + "name-api": "API", + "name-style": "スタイル", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", "placeholder": { "auto": "自動" } @@ -3669,20 +3697,20 @@ } } }, - "category-arrow-direction": "", - "category-background": "", - "category-border": "", - "category-canvas": "", - "category-color": "", - "category-data-links": "", - "category-line-style": "", - "category-metric-value": "", - "category-radius": "", - "category-selected-connection": "", - "category-selected-element": "", - "category-size": "", + "category-arrow-direction": "矢印の方向", + "category-background": "背景", + "category-border": "境界線", + "category-canvas": "キャンバス", + "category-color": "色", + "category-data-links": "データリンクとアクション", + "category-line-style": "線のスタイル", + "category-metric-value": "指標の値", + "category-radius": "半径", + "category-selected-connection": "選択した接続", + "category-selected-element": "選択した要素({{element}})", + "category-size": "サイズ", "cloud-item": { - "category-cloud": "", + "category-cloud": "クラウド", "label": { "bottom": "下", "center": "中央", @@ -3691,48 +3719,48 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } }, - "description-experimental-types": "", - "description-infinite-panning": "", - "description-inline-editing": "", - "description-pan-zoom": "", + "description-experimental-types": "実験的な要素タイプの選択を有効化", + "description-infinite-panning": "制限なしのパンを有効化 - 広大なキャンバスに便利。警告: これは実験的な機能で、現在は上部/左側に固定されている要素でのみ正常に動作します", + "description-inline-editing": "パネルの直接編集を有効化", + "description-pan-zoom": "パンとズームを有効化", "direction-options": { - "label-both": "", - "label-forward": "", - "label-none": "", - "label-reverse": "" + "label-both": "両方", + "label-forward": "前進", + "label-none": "なし", + "label-reverse": "反転" }, "drone-front-item": { - "category-drone-front": "", - "name-roll-angle": "" + "category-drone-front": "ドローン正面", + "name-roll-angle": "回転角度" }, "drone-side-item": { - "category-drone-side": "", - "name-pitch-angle": "" + "category-drone-side": "ドローン側面", + "name-pitch-angle": "ピッチ角" }, "drone-top-item": { - "category-drone-top": "", - "name-back-left-rotor-rpm": "", - "name-back-right-rotor-rpm": "", - "name-front-left-rotor-rpm": "", - "name-front-right-rotor-rpm": "", - "name-yaw-angle": "" + "category-drone-top": "ドローン上面", + "name-back-left-rotor-rpm": "左後部ローターRPM", + "name-back-right-rotor-rpm": "右後部ローターRPM", + "name-front-left-rotor-rpm": "左正面ローターRPM", + "name-front-right-rotor-rpm": "右正面ローターRPM", + "name-yaw-angle": "ヨー角" }, "element-editor": { - "category-layout": "", - "description-unsupported": "", - "name-quick-placement": "" + "category-layout": "レイアウト", + "description-unsupported": "選択した要素タイプは現在の設定でサポートされていません。高度な要素タイプを有効にしてください。", + "name-quick-placement": "クイック配置" }, "ellipse-item": { - "category-ellipse": "", + "category-ellipse": "楕円", "label": { "bottom": "下", "center": "中央", @@ -3741,50 +3769,50 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } }, "icon-item": { - "category-icon": "", - "name-fill-color": "", - "name-svg-path": "" + "category-icon": "アイコン", + "name-fill-color": "塗りつぶし色", + "name-svg-path": "SVGパス" }, "inline-edit": { "canvas-inline-editor": "キャンバスインラインエディター", "tooltip-close-inline-editor": "インラインエディターを閉じる" }, "inline-edit-body": { - "category-selected-element": "", + "category-selected-element": "選択した要素({{element}})", "label-add-item": "アイテムを追加", "please-select-an-element": "要素を選択してください" }, - "label-actions": "", - "label-color": "", - "label-direction": "", - "label-image": "", - "label-image-size": "", - "label-line-style": "", - "label-links": "", - "label-radius": "", - "label-size": "", - "label-width": "", + "label-actions": "アクション", + "label-color": "色", + "label-direction": "方向", + "label-image": "画像", + "label-image-size": "画像サイズ", + "label-line-style": "線のスタイル", + "label-links": "リンク", + "label-radius": "半径", + "label-size": "サイズ", + "label-width": "幅", "layer-editor": { - "category-layer": "", - "category-layout": "", - "name-constraints": "", - "name-elements": "" + "category-layer": "レイヤー", + "category-layout": "レイアウト", + "name-constraints": "制約", + "name-elements": "要素" }, "line-style-editor": { "label-animate": "アニメーション" }, "line-style-options": { - "label-solid": "" + "label-solid": "実線" }, "metric-value-item": { "label": { @@ -3799,15 +3827,15 @@ "auto": "自動" } }, - "name-align-text": "", - "name-color": "", - "name-experimental-types": "", - "name-infinite-panning": "", - "name-inline-editing": "", - "name-pan-zoom": "", - "name-text": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-color": "文字色", + "name-experimental-types": "実験的な要素タイプ", + "name-infinite-panning": "制限なしのパン", + "name-inline-editing": "インライン編集", + "name-pan-zoom": "パンとズーム", + "name-text": "テキスト", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "not-found-display": { "not-found": "<0>見つかりません:" }, @@ -3820,7 +3848,7 @@ "zoom-scroll-wheel": "ズーム: スクロールホイール" }, "parallelogram-item": { - "category-parallelogram": "", + "category-parallelogram": "平行四辺形", "label": { "bottom": "下", "center": "中央", @@ -3829,11 +3857,11 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } @@ -3846,21 +3874,21 @@ }, "placement-editor": { "horizontal-options": { - "label-center": "", - "label-left": "", - "label-left-and-right": "", - "label-right": "", - "label-scale": "" + "label-center": "中央", + "label-left": "左", + "label-left-and-right": "左と右", + "label-right": "右", + "label-scale": "スケール" }, "label-constraints": "制約", "label-position": "位置", "loading": "読み込み中...", "vertical-options": { - "label-bottom": "", - "label-center": "", - "label-scale": "", - "label-top": "", - "label-top-and-bottom": "" + "label-bottom": "下", + "label-center": "中央", + "label-scale": "スケール", + "label-top": "上", + "label-top-and-bottom": "上と下" } }, "quick-positioning": { @@ -3872,7 +3900,7 @@ "tooltip-align-vertical-centers": "垂直中央揃え" }, "rectangle-item": { - "category-rectangle": "", + "category-rectangle": "長方形", "label": { "bottom": "下", "center": "中央", @@ -3881,33 +3909,33 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } }, "server-item": { - "category-server": "", - "name-blink-rate": "", - "name-bulb-color": "", - "name-status-color": "", - "name-type": "", + "category-server": "サーバー", + "name-blink-rate": "点滅レート[hz](0 =オフ)", + "name-bulb-color": "電球の色", + "name-status-color": "ステータスの色", + "name-type": "タイプ", "type-options": { - "label-database": "", - "label-single": "", - "label-stack": "", - "label-terminal": "" + "label-database": "データベース", + "label-single": "単体", + "label-stack": "スタック", + "label-terminal": "ターミナル" } }, "text-display": { "double-click-to-set": "ダブルクリックしてテキストを設定" }, "text-item": { - "category-text": "", + "category-text": "テキスト", "label": { "bottom": "下", "center": "中央", @@ -3916,11 +3944,11 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } @@ -3942,7 +3970,7 @@ "tooltip-remove": "削除" }, "triangle-item": { - "category-triangle": "", + "category-triangle": "三角形", "label": { "bottom": "下", "center": "中央", @@ -3951,18 +3979,18 @@ "right": "右", "top": "上" }, - "name-align-text": "", - "name-text": "", - "name-text-color": "", - "name-text-size": "", - "name-vertical-align": "", + "name-align-text": "テキストの配置", + "name-text": "テキスト", + "name-text-color": "文字色", + "name-text-size": "文字サイズ", + "name-vertical-align": "垂直方向の配置", "placeholder": { "auto": "自動" } }, "wind-turbine-item": { - "category-wind-turbine": "", - "name-rpm": "" + "category-wind-turbine": "風力原動機", + "name-rpm": "RPM" } }, "carousel": { @@ -3979,8 +4007,8 @@ "close-button": { "tooltip": "閉じる" }, - "cloud-enterprise-feature-badge": "", - "cloud-feature-badge": "", + "cloud-enterprise-feature-badge": "クラウドとエンタープライズ", + "cloud-feature-badge": "クラウド", "combobox": { "async": { "error": "オプションの読み込み中にエラーが発生しました。" @@ -4510,7 +4538,7 @@ } }, "description": { - "action": "" + "action": "ダッシュボードの説明を変更" }, "dynamic-config-value-editor": { "render-label": { @@ -4519,12 +4547,16 @@ }, "edit-actions": { "add": "{{typeName}}を追加", - "move": "", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", + "move": "{{typeName}}を移動", "panel-background": "パネルの背景を変更", "panel-description": "パネルの説明を変更", - "panel-max-repeats-per-row": "", + "panel-max-repeats-per-row": "行あたりの最大繰り返し回数", "panel-repeat-direction": "方向を繰り返す", - "panel-repeat-variable": "", + "panel-repeat-variable": "パネルの繰り返し", "panel-title": "パネルのタイトルを変更", "paste-panel": "パネルを貼り付け", "remove": "{{typeName}}を削除", @@ -4533,6 +4565,7 @@ "edit-pane": { "elements": { "dashboard": "ダッシュボード", + "element": "", "local-variable": "ローカル変数", "multiple-elements": "複数の要素", "multiple-elements-delete-text": "これらの要素を削除してもよろしいですか?", @@ -4658,7 +4691,7 @@ "get-field-override-categories": { "label-add-field-override": "フィールドオーバーライドを追加", "label-add-override-property": "オーバーライドプロパティを追加", - "override-name": "", + "override-name": "{{overrideNum}}回上書き", "title": { "add-button": "追加ボタン" } @@ -4882,7 +4915,7 @@ "title-matched_other": "{{count}}/{{totalCount}}件のオプションが一致" }, "outline": { - "repeated-item": "", + "repeated-item": "繰り返し", "tree-item": { "empty": "(空白)", "no-title": "<タイトルなし>" @@ -5040,8 +5073,8 @@ "label-changes": "変更", "label-details": "詳細", "title": "ダッシュボードを保存", - "title-copy": "", - "title-provisioned": "" + "title-copy": "ダッシュボードのコピーを保存", + "title-provisioned": "プロビジョニング済みダッシュボード" }, "save-dashboard-error-proxy": { "body-name-exists": "選択したフォルダには同じ名前のダッシュボードがすでに存在します。<1><2>このダッシュボードの保存を続行しますか?", @@ -5118,7 +5151,7 @@ } }, "title": { - "action": "" + "action": "ダッシュボードのタイトルを変更" }, "toolbar": { "add": "追加", @@ -5553,12 +5586,12 @@ } }, "delete-provisioned-dashboard-form": { - "api-error": "", + "api-error": "ダッシュボードの削除に失敗しました", "cancel-action": "キャンセル", "delete-action": "ダッシュボードを削除する", - "delete-read-only-file-message": "", + "delete-read-only-file-message": "リポジトリが読み取り専用のため、このダッシュボードをGrafanaから直接削除することはできません。このダッシュボードを削除するには、Gitリポジトリからファイルを削除してください。", "deleting": "削除中…", - "drawer-title": "", + "drawer-title": "プロビジョニングされたダッシュボードを削除", "title-this-repository-is-read-only": "このリポジトリは読み取り専用です" }, "description-label": { @@ -5572,7 +5605,7 @@ } }, "edit-provisioned-dashboard-form": { - "success": "" + "success": "ダッシュボードの変更が正常に保存されました" }, "email-list": { "aria-label-emailmenu": "メールメニューを切り替え" @@ -5740,7 +5773,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5765,7 +5797,7 @@ } }, "panel-data-alerting-tab": { - "tab-label": "" + "tab-label": "アラート" }, "panel-data-alerting-tab-rendered": { "alert": { @@ -5776,14 +5808,14 @@ "title-dashboard-not-saved": "ダッシュボードは保存されていません" }, "panel-data-queries-tab": { - "tab-label": "" + "tab-label": "クエリ" }, "panel-data-queries-tab-rendered": { "add-query": "クエリを追加", "expression": "式" }, "panel-data-transformations-tab": { - "tab-label": "" + "tab-label": "変換" }, "panel-data-transformations-tab-rendered": { "add-another-transformation": "別の変換を追加", @@ -5886,8 +5918,8 @@ "label-changes": "変更", "label-details": "詳細", "title": "ダッシュボードを保存", - "title-copy": "", - "title-provisioned": "" + "title-copy": "ダッシュボードのコピーを保存", + "title-provisioned": "プロビジョニング済みダッシュボード" } }, "save-dashboard-form": { @@ -5906,8 +5938,8 @@ "title-someone-else-has-updated-this-dashboard": "他のユーザーがこのダッシュボードを更新しました", "would-still-dashboard": "このダッシュボードの保存を続行しますか?" }, - "schema-v2-library-panels-export": "動的ダッシュボード機能は実験的なもので、現在のダッシュボード動作と完全な機能互換性はありません。この機能は、ライブラリパネルをサポートしていない新しいスキーマ形式に基づいています。そのため、別のインスタンスで使用する目的でダッシュボードをエクスポートした場合、ライブラリパネルは含まれません。機能の<2>ライフサイクルが進むにつれ、これらをサポートする予定です。", - "schema-v2-library-panels-export-title": "現在ダッシュボードスキーマV2では、別のインスタンスでの使用目的でのライブラリパネルのエクスポートをサポートしていません", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "ダッシュボードが不可逆的に変更されました" }, "save-dashboard-form-common-options": { @@ -6210,18 +6242,18 @@ } }, "dashlist": { - "category-dashboard-list": "", - "name-folder": "", - "name-include-current-template-variables": "", - "name-include-current-time-range": "", - "name-max-items": "", - "name-query": "", - "name-recently-viewed": "", - "name-search": "", - "name-show-folder-names": "", - "name-show-headings": "", - "name-starred": "", - "name-tags": "" + "category-dashboard-list": "ダッシュボードリスト", + "name-folder": "フォルダ", + "name-include-current-template-variables": "現在のテンプレート変数値を含める", + "name-include-current-time-range": "現在の時間範囲を含める", + "name-max-items": "最大項目数", + "name-query": "クエリ", + "name-recently-viewed": "最近表示したもの", + "name-search": "検索", + "name-show-folder-names": "フォルダ名を表示", + "name-show-headings": "見出しを表示", + "name-starred": "スター付き", + "name-tags": "タグ" }, "data-source-list": { "empty-state": { @@ -6483,15 +6515,15 @@ "dimensions": { "background-size-editor": { "options": { - "label-contain": "", - "label-cover": "", - "label-fill": "", - "label-original": "", - "label-tile": "" + "label-contain": "全体を表示", + "label-cover": "カバー", + "label-fill": "塗りつぶし", + "label-original": "元のサイズ", + "label-tile": "タイル表示" } }, "color-dimension-editor": { - "label-fixed-color": "", + "label-fixed-color": "固定色", "noOptionsMessage-no-fields-found": "" }, "file-dropzone-custom-children": { @@ -6506,10 +6538,10 @@ "placeholder-search": "検索" }, "resource-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "文字列フィールドの結果を使用", + "description-fixed": "固定値", "label-field": "フィールド", - "label-fixed": "", + "label-fixed": "固定", "label-mappings": "マッピング", "label-source": "送信元" }, @@ -6526,16 +6558,16 @@ }, "scalar-dimension-editor": { "fixed-value-options": { - "label-fixed-values": "" + "label-fixed-values": "固定値" }, "label-limit": "制限", "label-value": "値", "noOptionsMessage-no-fields-found": "", "scalar-options": { - "description-clamped": "", - "description-mod": "", - "label-clamped": "", - "label-mod": "" + "description-clamped": "最大値と最小値に固定されたフィールド値を使用", + "description-mod": "最大値を法とした剰余値を使用", + "label-clamped": "クランプ", + "label-mod": "モジュロ" } }, "scale-dimension-editor": { @@ -6550,10 +6582,10 @@ "noOptionsMessage-no-fields-found": "" }, "text-dimension-editor": { - "description-field": "", - "description-fixed": "", + "description-field": "フィールド値を表示", + "description-fixed": "固定値", "label-field": "フィールド", - "label-fixed": "", + "label-fixed": "固定", "label-source": "送信元", "label-template": "テンプレート", "label-value": "値" @@ -6567,16 +6599,16 @@ "description-percentage-means-thresholds-relative": "パーセンテージは最小値と最大値に対する相対的なしきい値を意味します", "modes": { "description": { - "threshold-based-percent-between-minmax": "", - "thresholds-based-absolute-values": "" + "threshold-based-percent-between-minmax": "最小値と最大値の間の割合に基づいてしきい値を選択", + "thresholds-based-absolute-values": "絶対値に基づいてしきい値を選択" }, "label": { - "absolute": "", - "percentage": "" + "absolute": "絶対値", + "percentage": "割合" } }, "thresholds-mode": "しきい値モード", - "value-base": "" + "value-base": "基準" }, "urlpicker-tab": { "label-preview": "プレビュー" @@ -7110,6 +7142,7 @@ "start-time": "開始時間:" } }, + "share-span": "", "warnings": "警告" }, "span-filters": { @@ -7377,23 +7410,23 @@ "title": "このページを離れますか?" }, "gauge": { - "category-gauge": "", - "description-min-height": "", - "description-min-width": "", - "description-neutral": "", - "description-show-threshold-labels": "", - "description-show-threshold-markers": "", + "category-gauge": "ゲージ", + "description-min-height": "最小行の高さ(水平方向)", + "description-min-width": "最小列幅(垂直方向)", + "description-neutral": "中立点として最小値を使用するには、空のままにします", + "description-show-threshold-labels": "ゲージバーの周りのしきい値をレンダリングします", + "description-show-threshold-markers": "しきい値を外側のバーとしてレンダリングします", "gauge-size-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "自動", + "label-manual": "手動" }, - "name-gauge-size": "", - "name-min-height": "", - "name-min-width": "", - "name-neutral": "", - "name-show-threshold-labels": "", - "name-show-threshold-markers": "", - "placeholder-neutral": "" + "name-gauge-size": "ゲージサイズ", + "name-min-height": "最小の高さ", + "name-min-width": "最小幅", + "name-neutral": "中立", + "name-show-threshold-labels": "しきい値ラベルを表示", + "name-show-threshold-markers": "しきい値マーカーを表示", + "placeholder-neutral": "自動" }, "gen-ai": { "apply-suggestion": "適用", @@ -7689,6 +7722,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7727,85 +8163,85 @@ }, "builder": { "axis": { - "category-axis": "", - "color-label": "", + "category-axis": "軸", + "color-label": "色", "color-options": { - "label-series": "", - "label-text": "" + "label-series": "系列", + "label-text": "テキスト" }, "grid-line-options": { - "label-auto": "", - "label-off": "", - "label-on": "" + "label-auto": "自動", + "label-off": "オフ", + "label-on": "オン" }, - "name-centered-zero": "", - "name-grid-lines": "", - "name-label": "", - "name-placement": "", - "name-scale": "", - "name-show-border": "", - "name-soft-max": "", - "name-soft-min": "", - "name-width": "", - "placeholder-label": "", - "placeholder-soft-max": "", - "placeholder-soft-min": "", - "placeholder-width": "", + "name-centered-zero": "中央ゼロ", + "name-grid-lines": "グリッド線を表示", + "name-label": "ラベル", + "name-placement": "配置", + "name-scale": "スケール", + "name-show-border": "境界線を表示", + "name-soft-max": "ソフトマックス", + "name-soft-min": "ソフトミン", + "name-width": "幅", + "placeholder-label": "オプションのテキスト", + "placeholder-soft-max": "参照: 標準オプション > 最大", + "placeholder-soft-min": "参照: 標準オプション > 最小", + "placeholder-width": "自動", "scale-distribution-editor": { "distribution-options": { - "label-linear": "", - "label-log": "", - "label-symlog": "" + "label-linear": "線形", + "label-log": "対数", + "label-symlog": "Symlog" } } }, "legend": { - "category": "", - "description-values": "", + "category": "凡例", + "description-values": "凡例に表示する値または計算を選択", "mode-options": { - "label-list": "", - "label-table": "" + "label-list": "リスト", + "label-table": "テーブル" }, - "name-mode": "", - "name-placement": "", - "name-values": "", - "name-visibility": "", - "name-width": "", + "name-mode": "モード", + "name-placement": "配置", + "name-values": "値", + "name-visibility": "表示レベル", + "name-width": "幅", "placement-options": { - "label-bottom": "", - "label-right": "" + "label-bottom": "下", + "label-right": "右" } }, "stacking": { - "name-stack-series": "" + "name-stack-series": "系列を積み上げ" }, "text": { - "category-text-size": "", - "name-percent-change": "", - "name-title": "", - "name-value": "", - "placeholder-percent-change": "", - "placeholder-title": "", - "placeholder-value": "" + "category-text-size": "文字サイズ", + "name-percent-change": "変化率(%)", + "name-title": "タイトル", + "name-value": "値", + "placeholder-percent-change": "自動", + "placeholder-title": "自動", + "placeholder-value": "自動" }, "tooltip": { - "category": "", - "description-hover-proximity": "", + "category": "ツールチップ", + "description-hover-proximity": "カーソルをポイントにどれだけ近づけるとツールチップがトリガーされるか(ピクセル単位)", "modeOptions": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "すべて", + "label-hidden": "非表示", + "label-single": "単体" }, - "name-hide-zeros": "", - "name-hover-proximity": "", - "name-max-height": "", - "name-max-width": "", - "name-tooltip-mode": "", - "name-values-sort-order": "", + "name-hide-zeros": "ゼロを非表示", + "name-hover-proximity": "ホバー時の距離", + "name-max-height": "最大の高さ", + "name-max-width": "最大幅", + "name-tooltip-mode": "ツールチップモード", + "name-values-sort-order": "値のソート順", "sortOptions": { - "label-ascending": "", - "label-descending": "", - "label-none": "" + "label-ascending": "昇順", + "label-descending": "降順", + "label-none": "なし" } } }, @@ -7928,6 +8364,60 @@ "previous-label": "前月", "select-placeholder": "日付と時間の選択" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "閉じる" }, @@ -7967,19 +8457,33 @@ "tooltip-expand-all": "すべての行を広げる" }, "matchers-ui": { - "description-field-name-by-regex-matcher": "", - "description-fields-by-query": "", - "description-fields-with-name": "", - "description-fields-with-type": "", - "description-fields-with-value": "", + "description-field-name-by-regex-matcher": "正規表現に一致する名前のフィールドのプロパティを設定", + "description-fields-by-query": "特定のクエリからのフィールドのプロパティを設定", + "description-fields-with-name": "特定のフィールドのプロパティを設定", + "description-fields-with-type": "特定のタイプ(数値、文字列、ブール値)のフィールドのプロパティを設定", + "description-fields-with-value": "リデューサー条件付きフィールドのプロパティを設定", "field-name-picker": { - "placeholder": "" + "placeholder": "フィールドを選択" }, - "name-field-name-by-regex-matcher": "", - "name-fields-by-query": "", - "name-fields-with-name": "", - "name-fields-with-type": "", - "name-fields-with-value": "" + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, + "name-field-name-by-regex-matcher": "名前が正規表現と一致するフィールド", + "name-fields-by-query": "クエリによって返されるフィールド", + "name-fields-with-name": "名前付きフィールド", + "name-fields-with-type": "タイプ付きフィールド", + "name-fields-with-value": "値付きフィールド" }, "menu-item": { "keyboard-shortcut-label": "キーボードショートカット" @@ -8079,7 +8583,10 @@ "no-data": "" }, "no-values-label": "値がありません", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}}({{numRows}}行)" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}}({{numRows}}行)", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "タグ" @@ -8103,55 +8610,55 @@ "config": { "get-graph-field-options": { "axis-placement": { - "description-auto": "", - "label-auto": "", - "label-hidden": "", - "label-left": "", - "label-right": "" + "description-auto": "最初のフィールドを左側に、その他すべてを右側に", + "label-auto": "自動", + "label-hidden": "非表示", + "label-left": "左", + "label-right": "右" }, "bar-alignment": { - "description-after": "", - "description-before": "", - "description-center": "" + "description-after": "後", + "description-before": "前", + "description-center": "中央" }, "draw-style": { - "label-bars": "", - "label-lines": "", - "label-points": "" + "label-bars": "バー", + "label-lines": "線", + "label-points": "ポイント" }, "fill-gradient": { - "description-hue": "", - "description-opacity": "", - "description-scheme": "", - "label-hue": "", - "label-none": "", - "label-opacity": "", - "label-scheme": "" + "description-hue": "小さな色相グラデーション", + "description-opacity": "塗りつぶしの不透明度グラデーションを有効化", + "description-scheme": "配色を使用してグラデーションを定義", + "label-hue": "色相", + "label-none": "なし", + "label-opacity": "不透明度", + "label-scheme": "配色" }, "line-interpolation": { - "description-linear": "", - "description-smooth": "", - "description-step-after": "", - "description-step-before": "" + "description-linear": "線形", + "description-smooth": "スムーズ", + "description-step-after": "ステップの後", + "description-step-before": "ステップの前" }, "show-points": { - "description-auto": "", - "label-always": "", - "label-auto": "", - "label-never": "" + "description-auto": "密度が低いときにポイントを表示", + "label-always": "常時", + "label-auto": "自動", + "label-never": "なし" }, "stacking": { - "label-100": "", - "label-normal": "", - "label-off": "" + "label-100": "100%", + "label-normal": "通常", + "label-off": "オフ" }, "thresholds-display-mode": { - "label-dashed-lines": "", - "label-filled-regions": "", - "label-filled-regions-and-dashed-lines": "", - "label-filled-regions-and-lines": "", - "label-lines": "", - "label-off": "" + "label-dashed-lines": "線(破線)として", + "label-filled-regions": "塗りつぶし領域として", + "label-filled-regions-and-dashed-lines": "塗りつぶし領域と線(破線)として", + "label-filled-regions-and-lines": "塗りつぶし領域と線として", + "label-lines": "線として", + "label-off": "オフ" } } } @@ -8198,78 +8705,78 @@ }, "heatmap": { "calculate-from-data-options": { - "label-no": "", - "label-yes": "" + "label-no": "いいえ", + "label-yes": "はい" }, - "category-cell-display": "", - "category-colors": "", - "category-exemplars": "", - "category-heatmap": "", - "category-legend": "", - "category-tooltip": "", - "category-y-axis": "", + "category-cell-display": "セル表示", + "category-colors": "色", + "category-exemplars": "エグゼンプラー", + "category-heatmap": "ヒートマップ", + "category-legend": "凡例", + "category-tooltip": "ツールチップ", + "category-y-axis": "Y軸", "mode-options": { - "label-opacity": "", - "label-scheme": "" + "label-opacity": "不透明度", + "label-scheme": "配色" }, - "name-axis-label": "", - "name-axis-width": "", - "name-calculate-from-data": "", - "name-cell-gap": "", - "name-color": "", - "name-decimals": "", - "name-end-color-at-value": "", - "name-exponent": "", - "name-hide-cells-gt": "", - "name-hide-cells-lt": "", - "name-max-height": "", - "name-max-value": "", - "name-max-width": "", - "name-min-value": "", - "name-mode": "", - "name-placement": "", - "name-reverse": "", - "name-scale": "", - "name-scheme": "", - "name-show-color-scale": "", - "name-show-histogram": "", - "name-show-legend": "", - "name-start-color-from-value": "", - "name-steps": "", - "name-tick-alignment": "", - "name-tooltip-mode": "", - "name-unit": "", - "name-value-name": "", - "name-y-axis-scale": "", - "placeholder-axis-label": "", - "placeholder-axis-width": "", - "placeholder-decimals": "", - "placeholder-end-color-at-value": "", - "placeholder-hide-cells-gt": "", - "placeholder-hide-cells-lt": "", - "placeholder-max-value": "", - "placeholder-min-value": "", - "placeholder-start-color-from-value": "", - "placeholder-value-name": "", + "name-axis-label": "軸ラベル", + "name-axis-width": "軸の幅", + "name-calculate-from-data": "データから計算", + "name-cell-gap": "セル間隔", + "name-color": "色", + "name-decimals": "小数点以下", + "name-end-color-at-value": "次の値でカラースケールを終了", + "name-exponent": "指数", + "name-hide-cells-gt": "値が次の値以上のセルを非表示にする", + "name-hide-cells-lt": "値が次の値以下のセルを非表示にする", + "name-max-height": "最大の高さ", + "name-max-value": "最大値", + "name-max-width": "最大幅", + "name-min-value": "最小値", + "name-mode": "モード", + "name-placement": "配置", + "name-reverse": "反転", + "name-scale": "スケール", + "name-scheme": "配色", + "name-show-color-scale": "カラースケールを表示", + "name-show-histogram": "ヒストグラムを表示(Y軸)", + "name-show-legend": "凡例を表示", + "name-start-color-from-value": "次の値からカラースケールを開始", + "name-steps": "ステップ", + "name-tick-alignment": "目盛の配置", + "name-tooltip-mode": "ツールチップモード", + "name-unit": "単位", + "name-value-name": "値の名前", + "name-y-axis-scale": "Y軸スケール", + "placeholder-axis-label": "自動", + "placeholder-axis-width": "自動", + "placeholder-decimals": "自動", + "placeholder-end-color-at-value": "自動(最大)", + "placeholder-hide-cells-gt": "なし", + "placeholder-hide-cells-lt": "なし", + "placeholder-max-value": "自動", + "placeholder-min-value": "自動", + "placeholder-start-color-from-value": "自動(最小)", + "placeholder-value-name": "値", "placement-options": { - "label-hidden": "", - "label-left": "", - "label-right": "" + "label-hidden": "非表示", + "label-left": "左", + "label-right": "右" }, "scale-options": { - "label-exponential": "", - "label-linear": "" + "label-exponential": "指数関数", + "label-linear": "線形" }, "tick-alignment-options": { - "label-auto": "", - "label-bottom": "", - "label-middle": "", - "label-top": "" + "label-auto": "自動", + "label-bottom": "下(GE)", + "label-middle": "中央", + "label-top": "上(LE)" }, "tooltip-mode-options": { - "label-all": "", - "label-hidden": "", - "label-single": "" + "label-all": "すべて", + "label-hidden": "非表示", + "label-single": "単体" } }, "help-modal": { @@ -8329,14 +8836,14 @@ "troubleshooting-help": "トラブルシューティングのヘルプをリクエストするには、このパネルのスナップショットをGrafana Labsテクニカルサポートに送信してください。スナップショットには、クエリ応答データとパネル設定が含まれています。" }, "histogram": { - "category-histogram": "", + "category-histogram": "ヒストグラム", "histogram-panel": { "no-histogram-found-in-response": "応答にヒストグラムが見つかりません" }, - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-width": "", - "name-stacking": "" + "name-fill-opacity": "塗りつぶし不透明度", + "name-gradient-mode": "グラデーションモード", + "name-line-width": "線の太さ", + "name-stacking": "積み上げ" }, "inspector": { "inspect-data-tab": { @@ -8712,16 +9219,16 @@ } }, "logs": { - "category-logs": "", + "category-logs": "ログ", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "正確", + "label-none": "なし", + "label-numbers": "数値", + "label-signature": "署名" }, - "description-enable-infinite-scrolling": "", - "description-enable-syntax-highlighting": "", - "description-show-controls": "", + "description-enable-infinite-scrolling": "実験的機能。ログリストの一番下までスクロールして、さらに結果をリクエストします。", + "description-enable-syntax-highlighting": "事前定義された構文の色付けルールを使い、ログ行の該当部分をハイライト表示します", + "description-show-controls": "最後または最初のログ行にジャンプするコントロールを表示し、ログレベルでフィルタリングします", "fields": { "type": { "loki": { @@ -8732,8 +9239,8 @@ } }, "font-size-options": { - "label-default": "", - "label-small": "" + "label-default": "デフォルト", + "label-small": "小" }, "get-dataframe-fields": { "vars": { @@ -8780,58 +9287,64 @@ "tooltip-error": "エラー:{{errorMessage}}" }, "log-line-details": { - "clear-search": "", - "close": "", - "copy-shortlink": "", - "copy-to-clipboard": "", + "clear-search": "クリア", + "close": "ログの詳細を閉じる", + "copy-shortlink": "ショートリンクをコピー", + "copy-to-clipboard": "クリップボードにコピー", "displayed-fields-section": "", "fields": { - "adhoc-statistics": "", - "copy-value-to-clipboard": "", - "filter-out": "", - "filter-out-query": "", + "adhoc-statistics": "アドホック統計", + "copy-value-to-clipboard": "値をクリップボードにコピー", + "filter-out": "値を除外してフィルタリング", + "filter-out-query": "クエリ{{query}}で値を除外してフィルタリング", "toggle-field-button": { - "field-instead-message": "", - "hide-this-field": "" + "field-instead-message": "メッセージの代わりにこのフィールドを表示", + "hide-this-field": "このフィールドを非表示" } }, - "fields-section": "", - "hide-log-line": "", + "fields-section": "フィールド", + "hide-log-line": "ログ行を非表示", "inline-mode": "", - "links-section": "", + "links-section": "リンク", "log-line-field": "", - "log-line-section": "", + "log-line-section": "ログ行", "move-displayed-field-down": "", "move-displayed-field-up": "", - "no-details": "", - "pin-line": "", + "no-details": "表示するフィールドがありません。", + "open-assistant": "", + "pin-line": "ログをピン留め", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { - "no-results": "" + "no-results": "表示する結果がありません。" }, - "search-placeholder": "", - "show-context": "", - "show-log-line": "", + "search-placeholder": "フィールド名と値を検索", + "show-context": "コンテキストを表示", + "show-log-line": "ログ行を表示", "sidebar-mode": "", - "unpin-line": "" + "unpin-line": "ログのピン留めを解除" }, "log-line-menu": { "copy-link": "ログ行へのリンクをコピー", "copy-log": "ログ行をコピー", "hide-details": "ログの詳細を表示", "icon-label": "ログメニュー", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "ログをピン留めする", "show-context": "コンテキストを表示", "show-details": "ログの詳細を非表示", "unpin-from-outline": "ログのピン留めを解除する" }, "log-list-search": { - "close": "", - "filter": "", - "info": "", - "input-placeholder": "", - "next": "", - "prev": "" + "close": "検索を閉じる", + "filter": "一致するログをフィルタリング", + "info": "表示されたログ内の文字列をクライアント側で検索します。クエリフィルターと混同しないでください。このコンポーネントを使用して、ログ結果において特定の文字列を検索します。", + "input-placeholder": "ログ内を検索", + "next": "次の結果", + "prev": "前の結果" }, "log-row-context-modal": { "error-loading-log-more-logs": "その他のログを読み込む際にエラーが発生しました。", @@ -8886,7 +9399,7 @@ "escape-newlines": "ログ行で誤ってエスケープされた改行とタブシーケンスを修正", "font-size-default": "小さいフォントサイズを使用", "font-size-small": "デフォルトのフォントサイズを使用", - "hide-search": "", + "hide-search": "検索を閉じる", "hide-timestamps": "タイムスタンプを非表示", "hide-unique-labels": "一意のラベルを非表示", "newest-first": "最新のログ順に並び替え - クリックして最も古いログを最初に表示", @@ -8895,7 +9408,7 @@ "remove-escaping": "エスケープを削除", "scroll-bottom": "一番下までスクロール", "scroll-top": "一番上までスクロール", - "show-search": "", + "show-search": "ログ結果内を検索", "show-timestamps": "タイムスタンプを表示", "show-unique-labels": "一意のラベルを表示", "unwrap-lines": "行の折り返しを解除", @@ -8913,26 +9426,26 @@ "common-labels": "共通ラベル:" } }, - "name-common-labels": "", - "name-deduplication": "", + "name-common-labels": "共通ラベル", + "name-deduplication": "重複排除", "name-details-mode": "", "name-details-options": { "label-inline": "", "label-sidebar": "" }, - "name-enable-infinite-scrolling": "", - "name-enable-log-details": "", - "name-enable-syntax-highlighting": "", - "name-font-size": "", - "name-order": "", - "name-prettify-json": "", - "name-show-controls": "", - "name-time": "", - "name-unique-labels": "", - "name-wrap-lines": "", + "name-enable-infinite-scrolling": "無限スクロールを有効化", + "name-enable-log-details": "ログの詳細を有効化", + "name-enable-syntax-highlighting": "構文ハイライトを有効化", + "name-font-size": "フォントサイズ", + "name-order": "順番", + "name-prettify-json": "JSONを整形", + "name-show-controls": "コントロールを表示", + "name-time": "時間", + "name-unique-labels": "一意のラベル", + "name-wrap-lines": "行を折り返す", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "新しいものから表示", + "label-oldest-first": "古いものから表示" }, "out-of-range-message": { "end-of-the-selected-time-range": "選択した時間範囲の終わり。" @@ -8946,7 +9459,7 @@ "un-themed-log-details": { "aria-label-data-links": "データリンク", "aria-label-fields": "フィールド", - "aria-label-line": "", + "aria-label-line": "ログ行", "aria-label-log-level": "ログレベル", "aria-label-no-details": "詳細なし" }, @@ -8967,27 +9480,27 @@ } }, "logs-new": { - "category-logs": "", + "category-logs": "ログ", "deduplication-options": { - "label-exact": "", - "label-none": "", - "label-numbers": "", - "label-signature": "" + "label-exact": "正確", + "label-none": "なし", + "label-numbers": "数値", + "label-signature": "署名" }, - "description-infinite-scrolling": "", - "description-show-controls": "", - "description-syntax-highlighting": "", - "name-deduplication": "", - "name-enable-log-details": "", - "name-infinite-scrolling": "", - "name-order": "", - "name-show-controls": "", - "name-syntax-highlighting": "", - "name-time": "", - "name-wrap-lines": "", + "description-infinite-scrolling": "実験的機能。ログリストの一番下までスクロールして、さらに結果をリクエストします。", + "description-show-controls": "最後または最初のログ行にジャンプするコントロールを表示し、ログレベルでフィルタリングします", + "description-syntax-highlighting": "事前定義された構文の色付けルールを使い、ログ行の該当部分をハイライト表示します", + "name-deduplication": "重複排除", + "name-enable-log-details": "ログの詳細を有効化", + "name-infinite-scrolling": "無限スクロールを有効化", + "name-order": "順番", + "name-show-controls": "コントロールを表示", + "name-syntax-highlighting": "構文ハイライトを有効化", + "name-time": "時間", + "name-wrap-lines": "行を折り返す", "order-options": { - "label-newest-first": "", - "label-oldest-first": "" + "label-newest-first": "新しいものから表示", + "label-oldest-first": "古いものから表示" } }, "manage-dashbaords": { @@ -9653,7 +10166,9 @@ "aria-label": "ヘルプ" }, "invite-user": { - "invite-new-member-button": "新しいメンバーの招待" + "invite-button": "招待", + "invite-new-member-button": "新しいメンバーの招待", + "invite-tooltip": "" }, "item": { "add-bookmark": "ブックマークに追加", @@ -9685,12 +10200,12 @@ "rss-button": "最新ブログから" }, "news": { - "category-news": "", - "description-show-image": "", - "description-url": "", + "category-news": "ニュース", + "description-show-image": "ニュースアイテムのSNS(og:image)画像をテキストコンテンツの上に表示するかどうかを制御", + "description-url": "RSSおよびAtomフィードをサポート", "link-title": "Grafana Labsブログに移動", - "name-show-image": "", - "name-url": "", + "name-show-image": "画像を表示", + "name-url": "URL", "news-panel": { "body-error-loading-rss-feed": "フィードURLが正しく、サーバー上でCORSが正しく設定されていることを確認してください。<2>ニュースパネルのドキュメントを参照してください。", "loading": "読み込み中...", @@ -9699,27 +10214,27 @@ "title": "最新ブログから" }, "node-graph": { - "category-edges": "", - "category-node-graph": "", - "category-nodes": "", + "category-edges": "エッジ", + "category-node-graph": "ノードグラフ", + "category-nodes": "ノード", "layout-algorithm-options": { - "description-force": "", - "description-grid": "", - "description-layered": "", - "label-force": "", - "label-grid": "", - "label-layered": "" + "description-force": "力学レイアウトを使用", + "description-grid": "グリッドレイアウトを使用", + "description-layered": "階層レイアウトを使用", + "label-force": "力学", + "label-grid": "グリッド", + "label-layered": "階層" }, - "name-arc-sections": "", - "name-layout-algorithm": "", - "name-main-stat-unit": "", - "name-secondary-stat-unit": "", - "name-zoom-mode": "", + "name-arc-sections": "円弧セクション", + "name-layout-algorithm": "レイアウトアルゴリズム", + "name-main-stat-unit": "メイン統計単位", + "name-secondary-stat-unit": "補助統計単位", + "name-zoom-mode": "ズームモード", "zoom-mode-options": { - "description-cooperative": "", - "description-greedy": "", - "label-cooperative": "", - "label-greedy": "" + "description-cooperative": "ページを通常どおりスクロールできます", + "description-greedy": "すべてのズームジェスチャーに反応します", + "label-cooperative": "協調型", + "label-greedy": "即時応答型" } }, "nodeGraph": { @@ -9795,40 +10310,40 @@ }, "registry": { "get-all-option-editors": { - "description-time-zone": "", - "name-time-zone": "" + "description-time-zone": "タイムゾーンの選択", + "name-time-zone": "タイムゾーン" }, "standard-field-condigs": { - "category-data-links": "" + "category-data-links": "データリンクとアクション" }, "standard-field-configs": { - "category": "", - "category-thresholds": "", - "category-value-mappings": "", - "description-display-name": "", - "description-field-min-max": "", - "description-max": "", - "description-min": "", - "description-no-value": "", - "description-value-mappings": "", - "name-actions": "", - "name-ad-hoc": "", - "name-color-scheme": "", - "name-data-links": "", - "name-decimals": "", - "name-display-name": "", - "name-field-min-max": "", - "name-max": "", - "name-min": "", - "name-no-value": "", - "name-thresholds": "", - "name-unit": "", - "name-value-mappings": "", - "placeholder-decimals": "", - "placeholder-display-name": "", - "placeholder-max": "", - "placeholder-min": "", - "placeholder-unit": "" + "category": "標準オプション", + "category-thresholds": "しきい値", + "category-value-mappings": "値のマッピング", + "description-display-name": "フィールドまたは系列名を変更", + "description-field-min-max": "フィールドごとに最小値と最大値を計算", + "description-max": "すべての値に基づいて計算するには、空のままにします", + "description-min": "すべての値に基づいて計算するには、空のままにします", + "description-no-value": "値がない場合に表示するものです", + "description-value-mappings": "入力値に基づいて表示テキストを変更します", + "name-actions": "アクション", + "name-ad-hoc": "アドホックフィルタリング可能", + "name-color-scheme": "配色", + "name-data-links": "データリンク", + "name-decimals": "小数点以下", + "name-display-name": "表示名", + "name-field-min-max": "フィールドの最小/最大", + "name-max": "最大", + "name-min": "最小", + "name-no-value": "値なし", + "name-thresholds": "しきい値", + "name-unit": "単位", + "name-value-mappings": "値のマッピング", + "placeholder-decimals": "自動", + "placeholder-display-name": "なし", + "placeholder-max": "自動", + "placeholder-min": "自動", + "placeholder-unit": "なし" } }, "units": { @@ -9937,13 +10452,13 @@ }, "panel-data-error-view": { "missing-value": { - "number": "", - "string": "", - "time": "", - "unknown": "" + "number": "データに数値フィールドがありません", + "string": "データに文字列フィールドがありません", + "time": "データに時間フィールドがありません", + "unknown": "データを視覚化できません" }, "no-value": { - "default": "" + "default": "データなし" }, "open-visualization-suggestions": "可視化に関する提案を開く", "switch-to-table": "テーブルに切り替え" @@ -9983,25 +10498,25 @@ "select-placeholder": "タイプで絞り込む" }, "piechart": { - "category-legend": "", - "category-pie-chart": "", - "description-labels": "", - "description-pie-chart-type": "", + "category-legend": "凡例", + "category-pie-chart": "円グラフ", + "description-labels": "円グラフに表示するラベルを選択", + "description-pie-chart-type": "円グラフのレンダリング方法", "labels-options": { - "label-name": "", - "label-percent": "", - "label-value": "" + "label-name": "名前", + "label-percent": "パーセント", + "label-value": "値" }, "legend-values-options": { - "label-percent": "", - "label-value": "" + "label-percent": "パーセント", + "label-value": "値" }, - "name-labels": "", - "name-legend-values": "", - "name-pie-chart-type": "", + "name-labels": "ラベル", + "name-legend-values": "凡例値", + "name-pie-chart-type": "円グラフの種類", "pie-chart-type-options": { - "label-donut": "", - "label-pie": "" + "label-donut": "ドーナツ", + "label-pie": "円" } }, "playlist": { @@ -10169,7 +10684,7 @@ "dependencies": "依存関係", "documentation": "ドキュメント", "downloads": "ダウンロード", - "from": "", + "from": "開始", "installedVersion": "インストールされているバージョン", "lastCommitDate": "最終コミット日:", "latestReleaseDate": "最新リリース日:", @@ -10239,7 +10754,7 @@ }, "install-controls-warning": { "body-not-published": "このプラグインは<2>grafana.com/pluginsに公開されておらず、カタログから管理できません。", - "enterprise-plugin-info": "", + "enterprise-plugin-info": "このプラグインはGrafana CloudとGrafana Enterpriseでのみ利用可能です。", "learn-more": "もっと詳しく", "no-valid-grafana-enterprise-license-detected": "有効なGrafana Enterpriseライセンスが検出されませんでした。", "title-dev-alert": "こちらはプラグインの開発ビルドであるため、アンインストールできません。", @@ -10397,28 +10912,50 @@ }, "provisioned-resource-form": { "save-or-delete-resource-shared-fields": { - "comment-placeholder-describe-changes-optional": "", - "description-branch-name-in-git-hub": "", + "comment-placeholder-describe-changes-optional": "変更内容を説明するメモを追加(任意)", + "description-branch-name-in-git-hub": "GitHubのブランチ名", "description-inside-repository": "", - "label-branch": "", - "label-comment": "", - "label-path": "", - "label-workflow": "" + "label-branch": "ブランチ", + "label-comment": "コメント", + "label-path": "パス", + "label-workflow": "ワークフロー" } }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "この機能は現在開発中です。最高の体験と最新の改善を利用するには、Grafanaの<2>ナイトリービルドの使用をお勧めします。" }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "このリポジトリ接続にわかりやすい名前を追加します", "empty": "空", @@ -10428,7 +10965,9 @@ "label-display-name": "表示名", "placeholder-my-repository-connection": "自分のリポジトリ接続", "text-loading-resource-information": "リソース情報読み込み中...", - "files-count_other": "{{count}}個のファイル" + "dashboards-count_other": "", + "files-count_other": "{{count}}個のファイル", + "folders-count_other": "" }, "check-repository": { "check": "確認" @@ -10439,36 +10978,20 @@ "button-save": "保存", "button-saving": "保存中…", "description-enabled": "自動取得を有効にすると、対象を変更できなくなります。", - "description-path": "Gitリポジトリ内のサブディレクトリへのパス", "description-read-only": "Grafanaではリソースを変更できません。", - "description-repository-url": "GitHubリポジトリURLを入力してください", "description-title": "わかりやすい設定名", "error-required": "このフィールドは必須です。", "error-save-repository": "リポジトリ設定の保存に失敗しました", - "error-valid-github-url": "有効なGitHubリポジトリURLを入力してください", "label-automatic-pulling": "自動取得", - "label-branch": "ブランチ", "label-enabled": "有効化", - "label-github-token": "GitHubトークン", "label-interval-seconds": "間隔(秒)", - "label-local-path": "ローカルパス", - "label-path": "パス", - "label-pr-workflow": "保存時にプルリクエストオプションを有効にする", "label-repository-type": "リポジトリタイプ", - "label-repository-url": "リポジトリURL", "label-target": "ターゲット", "label-title": "タイトル", "option-entire-instance": "インスタンス全体", - "option-github": "GitHub", - "option-local": "ローカル", "option-managed-folder": "管理フォルダ", - "placeholder-branch": "メイン", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "私の設定", - "placeholder-select-repository-type": "リポジトリタイプを選択" + "placeholder-my-config": "私の設定" }, "config-form-github-collapse": { "description-realtime-feedback": "変更がコミットされたらすぐにGrafanaでリアルタイムに更新を受け取れるように、<0>Webhookを設定します。変更が公開される前に、プルリクエストを使用して変更を確認・承認します。", @@ -10482,27 +11005,8 @@ }, "connect-repository-button": { "configure": "構成", - "configure-file": "ファイルのプロビジョニングを設定", - "configure-git-sync": "Git Syncを設定", "repository-limit-info-alert": "リポジトリの上限に達しました({{count}})" }, - "connect-step": { - "description-branch": "GitHubリポジトリで使用するブランチ", - "description-github-path": "これはダッシュボードが保存・プロビジョニングされるGitHubリポジトリ内のサブディレクトリへのパスです", - "description-paste-your-git-hub-personal-access-token": "GitHubの個人アクセストークンを貼り付けてください", - "description-repository-url": "GitHubリポジトリのURLを貼り付けてください", - "error-field-required": "このフィールドは必須です。", - "error-invalid-github-url": "有効なGitHubリポジトリURLを入力してください", - "label-access-token": "GitHubアクセストークン", - "label-branch": "ブランチ名", - "label-local-path": "ローカルパス", - "label-path": "リポジトリ内のサブディレクトリへのパス", - "label-repository-url": "GithubのリポジトリURL", - "placeholder-branch": "メイン", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "削除", "confirm-delete-repository": "リポジトリ設定を削除してもよろしいですか?", @@ -10534,17 +11038,17 @@ "title-instant-updates-requests-webhooks": "Webhookによるリアルタイム更新とプルリクエスト。", "title-visual-previews-in-pull-requests": "画像レンダリングによるプルリクエストの視覚的プレビュー" }, + "error-title-default": "", "expanded-row": { "job-specification": "ジョブ仕様", - "summary": "概要", - "title-error": "エラー" + "summary": "概要" }, "features-list": { "actions": { "set-up-required-feature-toggles": "必要な機能切り替えを設定" }, - "manage-dashboards-provision-updates-automatically": "GitHubでコードとしてダッシュボードを管理し、更新を自動的にプロビジョニング", - "manage-your-dashboards-with-remote-provisioning": "GitSyncの利用を開始", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "バージョン管理されたストレージにダッシュボードを保存して、整理と履歴追跡を改善" }, "file-history-page": { @@ -10565,18 +11069,16 @@ }, "finish-step": { "description-enable-previews": "プルリクエストによるダッシュボードの変更の画像プレビューを追加します。Grafanaダッシュボードの画像はGitリポジトリで共有され、リポジトリにアクセスできる全ユーザーが閲覧できます。", + "description-generate-dashboard-previews": "", "description-image-rendering": "画像レンダリングが必要です。<2>画像レンダリングを設定", - "description-often-shall-instance-updates-git-hub": "インスタンスはどのくらいの頻度でGitHubから更新を取得しますか?", - "description-pr-enable-description": "ユーザーが変更保存時にプルリクエストを開くかどうかを選択できるようにします。リポジトリがメインブランチへの直接変更を許可していない場合でも、プルリクエストが必要になる可能性があります。", + "description-preview-requirements": "", "description-read-only": "Grafanaではリソースを変更できません。", - "description-webhooks-enable": "ユーザーが変更保存時にプルリクエストを開くかどうかを選択できるようにします。リポジトリがメインブランチへの直接変更を許可していない場合でも、プルリクエストが必要になる可能性があります。", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "プルリクエストでダッシュボードのプレビューを有効にする", - "label-pr-workflow": "保存時にプルリクエストオプションを有効にする", + "label-generate-dashboard-previews": "", "label-read-only": "読み出しのみ", - "label-update-instance-interval-seconds": "インスタンス間隔の更新(秒)", - "placeholder": "60", - "text-setup-later": "こちらは後からいつでも設定できます", - "title-enhance-github": "GitHub体験を向上させる" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "クエリに一致する結果なし", @@ -10607,6 +11109,57 @@ "header": "プロビジョニング", "subtitle-provisioning-feature": "プロビジョニング接続を表示・管理" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "見つかりません" }, @@ -10633,9 +11186,17 @@ "starting": "開始中...", "status": { "title-error-running-job": "実行エラー", - "title-job-completed-successfully": "ジョブが正常に完了しました" + "title-warning-running-job": "" }, - "summary": "概要" + "summary": "概要", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10661,7 +11222,7 @@ }, "repository-actions": { "settings": "設定", - "source-code": "ソースコード" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10726,6 +11287,24 @@ "title-legacy-storage": "レガシーストレージ", "title-queued-for-deletion": "削除待ちリストに追加されました" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "ベース", "dashboard-preview": "ダッシュボードプレビュー", @@ -10755,6 +11334,11 @@ "label-current-step": "現在のステップ", "label-pending-step": "保留中のステップ" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "設定を編集", "button-edit": "編集", @@ -10765,9 +11349,6 @@ "tooltip-unhealthy-repository": "問題のあるリポジトリをプルできません" }, "synchronize-step": { - "error-no-job-id": "ジョブを開始できませんでした", - "error-no-repository-name": "リポジトリ名が指定されていません", - "error-starting-job": "ジョブ開始時のエラー", "synchronization-description": "各履歴値のコミットを含める", "synchronization-options": "同期オプション" }, @@ -10776,6 +11357,7 @@ "go-to": "進む", "make-sure": "これらの権限を必ず含めてください" }, + "warning-title-default": "", "wizard": { "alert-point-1": "このプロセス中はリソースの作成、編集、削除を行えません。最後のステップになると、リソースが消えます。", "alert-point-2": "プロビジョニングが完了すると、リソースが再表示され、外部ストレージを通じて管理されます。", @@ -11677,112 +12259,112 @@ }, "stat": { "add-orientation-option": { - "description-orientation": "", - "name-orientation": "", + "description-orientation": "レイアウトの向き", + "name-orientation": "向き", "orientation-options": { - "label-auto": "", - "label-horizontal": "", - "label-vertical": "" + "label-auto": "自動", + "label-horizontal": "水平", + "label-vertical": "垂直" } }, "add-standard-data-reduce-options": { - "category-value-options": "", - "description-calculation": "", - "description-fields": "", - "description-limit": "", - "description-show": "", + "category-value-options": "値のオプション", + "description-calculation": "リデューサー関数/計算を選択", + "description-fields": "パネルに含めるフィールドを選択", + "description-limit": "表示する最大行数", + "description-show": "列または系列ごとに単一の値を計算するか、各行を表示", "fields-options": { - "label-all-fields": "", - "label-numeric-fields": "" + "label-all-fields": "すべてのフィールド", + "label-numeric-fields": "数値フィールド" }, - "name-calculation": "", - "name-fields": "", - "name-limit": "", - "name-show": "", + "name-calculation": "計算", + "name-fields": "フィールド", + "name-limit": "制限", + "name-show": "表示", "show-options": { - "label-all-values": "", - "label-calculate": "" + "label-all-values": "すべての値", + "label-calculate": "計算" } }, - "category-stat-styles": "", + "category-stat-styles": "統計スタイル", "color-mode-options": { - "label-background-gradient": "", - "label-background-solid": "", - "label-none": "", - "label-value": "" + "label-background-gradient": "背景グラデーション", + "label-background-solid": "背景ソリッド", + "label-none": "なし", + "label-value": "値" }, - "description-graph-mode": "", - "description-text-mode": "", + "description-graph-mode": "統計パネルグラフ/スパークラインモード", + "description-text-mode": "名前と値を表示するか、名前のみを表示するかを制御", "graph-mode": { "options": { - "label-area": "", - "label-none": "" + "label-area": "面積", + "label-none": "なし" } }, "name-color-modcolor-mode-options": { - "label": "" + "label": "カラーモード" }, - "name-graph-mode": "", - "name-show-percent-change": "", - "name-text-alignment": "", - "name-text-mode": "", - "name-wide-layout": "", - "percent-change-color-mode": "", + "name-graph-mode": "グラフモード", + "name-show-percent-change": "変化率を表示", + "name-text-alignment": "テキストの配置", + "name-text-mode": "テキストモード", + "name-wide-layout": "ワイドレイアウト", + "percent-change-color-mode": "変化率カラーモード", "percent-change-color-mode-options": { - "label-inverted": "", - "label-same-as-value": "", - "label-standard": "" + "label-inverted": "反転", + "label-same-as-value": "値と同じ", + "label-standard": "標準" }, "text-alignment-options": { - "label-auto": "", - "label-center": "" + "label-auto": "自動", + "label-center": "中央" }, "text-mode-options": { - "label-auto": "", - "label-name": "", - "label-none": "", - "label-value": "", - "label-value-and-name": "" + "label-auto": "自動", + "label-name": "名前", + "label-none": "なし", + "label-value": "値", + "label-value-and-name": "値と名前" }, "wide-layout-options": { - "label-off": "", - "label-on": "" + "label-off": "オフ", + "label-on": "オン" } }, "state-timeline": { "align-values-options": { - "label-center": "", - "label-left": "", - "label-right": "" + "label-center": "中央", + "label-left": "左", + "label-right": "右" }, - "category-state-timeline": "", - "name-align-values": "", - "name-connect-null-values": "", - "name-disconnect-values": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-merge-equal-consecutive-values": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-state-timeline": "状態タイムライン", + "name-align-values": "値を整列", + "name-connect-null-values": "null値を接続", + "name-disconnect-values": "値の切断", + "name-fill-opacity": "塗りつぶし不透明度", + "name-line-width": "線の太さ", + "name-merge-equal-consecutive-values": "等しい連続値を統合", + "name-page-size": "ページサイズ(ページ分割を有効化)", + "name-row-height": "行の高さ", + "name-show-values": "値を表示", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "常時", + "label-auto": "自動", + "label-never": "なし" } }, "status-history": { - "category-status-history": "", - "name-column-width": "", - "name-fill-opacity": "", - "name-line-width": "", - "name-page-size": "", - "name-row-height": "", - "name-show-values": "", + "category-status-history": "ステータス履歴", + "name-column-width": "列幅", + "name-fill-opacity": "塗りつぶし不透明度", + "name-line-width": "線の太さ", + "name-page-size": "ページサイズ(ページ分割を有効化)", + "name-row-height": "行の高さ", + "name-show-values": "値を表示", "show-values-options": { - "label-always": "", - "label-auto": "", - "label-never": "" + "label-always": "常時", + "label-auto": "自動", + "label-never": "なし" }, "status-history-panel": { "too-many-points_other": "ポイントが多すぎて適切に可視化できません。<1>より少ないポイントが返されるようにクエリを更新してください。<3>({{count}}ポイントを受信)" @@ -11821,13 +12403,13 @@ "label-gauge-display-mode": "ゲージ表示モード", "label-value-display": "値を表示" }, - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "セルオプション", + "category-table": "テーブル", + "category-table-footer": "テーブルフッター", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "大", + "label-medium": "中", + "label-small": "小" }, "color-background-cell-options-editor": { "description-apply-to-entire-row": "選択されている場合、行全体がこのセルと同じ色で表示されます。", @@ -11837,26 +12419,25 @@ }, "label-apply-to-entire-row": "行全体に適用", "label-background-display-mode": "背景表示モード", - "label-wrap-text": "テキストを折り返す", "wrap-text": "テキストを折り返す" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "自動", + "label-center": "中央", + "label-left": "左", + "label-right": "右" }, "container": { "content": "1つのテーブルで多すぎる列を表示すると、パフォーマンスに影響を与え、データの読み取りが困難になる可能性があります。クエリを絞り込むことを検討してください。", "show-all-series": "すべての列を表示", "show-only-series": "{{MAX_NUMBER_OF_COLUMNS}}件の列のみ表示" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", + "description-calculation": "リデューサー関数/計算を選択", + "description-cell-value-inspect": "モーダルウィンドウでセル値の検査を有効化", + "description-column-filter": "テーブルのフィールドフィルターを有効/無効化", + "description-count-rows": "すべてのデータ行の単一カウントを表示", + "description-fields": "計算するフィールドを選択", + "description-min-column-width": "列の自動サイズ変更の最小幅", "description-wrap-header-text": "", "image-cell-options-editor": { "description-alt-text": "画像が表示できない場合やスクリーンリーダーを使用するユーザーのために表示される代替テキスト", @@ -11864,61 +12445,61 @@ "label-alt-text": "代替テキスト", "label-title-text": "タイトルテキスト" }, - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-paginations": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "name-wrap-header-text": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "name-calculation": "計算", + "name-cell-height": "セルの高さ", + "name-cell-type": "セルの種類", + "name-cell-value-inspect": "セル値の検査", + "name-column-alignment": "列の配置", + "name-column-filter": "列フィルター", + "name-column-width": "列幅", + "name-count-rows": "行数", + "name-enable-paginations": "ページ分割を有効化", + "name-fields": "フィールド", + "name-hide-in-table": "テーブルで非表示", + "name-min-column-width": "最小列幅", + "name-show-table-footer": "テーブルフッターを表示", + "name-show-table-header": "テーブルヘッダーを表示", + "name-wrap-header-text": "ヘッダーテキストの折り返し", + "placeholder-column-width": "自動", + "placeholder-fields": "すべての数値フィールド" }, "table-new": { - "category-cell-options": "", - "category-table": "", - "category-table-footer": "", + "category-cell-options": "セルオプション", + "category-table": "テーブル", + "category-table-footer": "テーブルフッター", "cell-height-options": { - "label-large": "", - "label-medium": "", - "label-small": "" + "label-large": "大", + "label-medium": "中", + "label-small": "小" }, "column-alignment-options": { - "label-auto": "", - "label-center": "", - "label-left": "", - "label-right": "" + "label-auto": "自動", + "label-center": "中央", + "label-left": "左", + "label-right": "右" }, - "description-calculation": "", - "description-cell-value-inspect": "", - "description-column-filter": "", - "description-count-rows": "", - "description-fields": "", - "description-min-column-width": "", - "name-calculation": "", - "name-cell-height": "", - "name-cell-type": "", - "name-cell-value-inspect": "", - "name-column-alignment": "", - "name-column-filter": "", - "name-column-width": "", - "name-count-rows": "", - "name-enable-pagination": "", - "name-fields": "", - "name-hide-in-table": "", - "name-min-column-width": "", - "name-show-table-footer": "", - "name-show-table-header": "", - "placeholder-column-width": "", - "placeholder-fields": "" + "description-calculation": "リデューサー関数/計算を選択", + "description-cell-value-inspect": "モーダルウィンドウでセル値の検査を有効化", + "description-column-filter": "テーブルのフィールドフィルターを有効/無効化", + "description-count-rows": "すべてのデータ行の単一カウントを表示", + "description-fields": "計算するフィールドを選択", + "description-min-column-width": "列の自動サイズ変更の最小幅", + "name-calculation": "計算", + "name-cell-height": "セルの高さ", + "name-cell-type": "セルの種類", + "name-cell-value-inspect": "セル値の検査", + "name-column-alignment": "列の配置", + "name-column-filter": "列フィルター", + "name-column-width": "列幅", + "name-count-rows": "行数", + "name-enable-pagination": "ページ分割を有効化", + "name-fields": "フィールド", + "name-hide-in-table": "テーブルで非表示", + "name-min-column-width": "最小列幅", + "name-show-table-footer": "テーブルフッターを表示", + "name-show-table-header": "テーブルヘッダーを表示", + "placeholder-column-width": "自動", + "placeholder-fields": "すべての数値フィールド" }, "tag-filter": { "clear-button": "タグをクリア", @@ -12003,17 +12584,17 @@ } }, "text": { - "category-text": "", + "category-text": "テキスト", "mode-options": { - "label-code": "", - "label-html": "", - "label-markdown": "" + "label-code": "コード", + "label-html": "HTML", + "label-markdown": "Markdown" }, - "name-content": "", - "name-language": "", - "name-mode": "", - "name-show-line-numbers": "", - "name-show-mini-map": "" + "name-content": "内容", + "name-language": "言語", + "name-mode": "モード", + "name-show-line-numbers": "行番号を表示", + "name-show-mini-map": "ミニマップを表示" }, "theme-preview": { "breadcrumbs": { @@ -12061,7 +12642,7 @@ "apply-button": "時間範囲を適用", "default-error": "過去の日付または「{{now}}」を入力してください", "fiscal-year": "会計年度:{{from}}~{{to}}", - "from-input": "", + "from-input": "開始", "open-input-calendar": "カレンダー", "range-error": "「開始月」を「終了月」より後にすることはできません", "to-input": "終了月" @@ -12079,7 +12660,7 @@ "aria-role": "時間範囲の選択", "default-title": "時間範囲", "example-title": "時間範囲の例", - "from-label": "", + "from-label": "開始", "from-to": "{{timeOptionFrom}}から{{timeOptionTo}}まで", "specify": "時間範囲を指定", "submit-button-label": "タイムピッカー送信ボタン", @@ -12092,8 +12673,8 @@ }, "timeline": { "missing-field": { - "all": "", - "time": "" + "all": "グラフ化可能なフィールドはありません", + "time": "データに時間フィールドがありません" } }, "timeseries": { @@ -12111,54 +12692,54 @@ "tooltip-delete": "削除", "tooltip-edit": "編集" }, - "category-axis": "", + "category-axis": "軸", "config": { "get-graph-field-config": { - "aria-label-bar-width-factor": "", - "aria-label-fill-opacity": "", - "aria-label-line-width": "", - "aria-label-point-size": "", - "category-graph-styles": "", - "category-thresholds": "", - "name-bar-alignment": "", - "name-bar-width-factor": "", - "name-connect-nulls": "", - "name-disconnect-values": "", - "name-fill-below-to": "", - "name-fill-opacity": "", - "name-gradient-mode": "", - "name-line-interpolation": "", - "name-line-style": "", - "name-line-width": "", - "name-point-size": "", - "name-show-points": "", - "name-show-thresholds": "", - "name-style": "", - "name-transform": "", + "aria-label-bar-width-factor": "バー幅係数", + "aria-label-fill-opacity": "塗りつぶし不透明度", + "aria-label-line-width": "線の太さ", + "aria-label-point-size": "ポイントサイズ", + "category-graph-styles": "グラフスタイル", + "category-thresholds": "しきい値", + "name-bar-alignment": "バーの配置", + "name-bar-width-factor": "バー幅係数", + "name-connect-nulls": "null値を接続", + "name-disconnect-values": "値の切断", + "name-fill-below-to": "以下を塗りつぶし", + "name-fill-opacity": "塗りつぶし不透明度", + "name-gradient-mode": "グラデーションモード", + "name-line-interpolation": "線補間", + "name-line-style": "線のスタイル", + "name-line-width": "線の太さ", + "name-point-size": "ポイントサイズ", + "name-show-points": "ポイントを表示", + "name-show-thresholds": "しきい値を表示", + "name-style": "スタイル", + "name-transform": "変換", "transform-options": { - "description-constant": "", - "description-style": "", - "label-constant": "", - "label-style": "" + "description-constant": "最初の値は定数線として表示されます", + "description-style": "結果をY軸の負の値に反転表示", + "label-constant": "定数", + "label-style": "Y軸を反転" } } }, "insert-nulls-editor": { "disconnect-options": { - "label-never": "", - "label-threshold": "" + "label-never": "なし", + "label-threshold": "しきい値" } }, "line-style-editor": { "line-fill-options": { - "label-dash": "", - "label-dots": "", - "label-solid": "" + "label-dash": "破線", + "label-dots": "点線", + "label-solid": "実線" }, "title-the-input-expects-a-segment-list": "入力にはセグメントリストが必要です", "tooltip-help": "ヘルプ" }, - "name-time-zone": "", + "name-time-zone": "タイムゾーン", "nulls-threshold-input": { "placeholder-never": "なし" }, @@ -12168,9 +12749,9 @@ }, "span-nulls-editor": { "gaps-options": { - "label-always": "", - "label-never": "", - "label-threshold": "" + "label-always": "常時", + "label-never": "なし", + "label-threshold": "しきい値" } }, "timezones-editor": { @@ -12179,17 +12760,17 @@ } }, "traces": { - "category-span-filters": "", - "name-critical-path-only": "", - "name-find-in-trace": "", - "name-max-duration": "", - "name-min-duration": "", - "name-service-name": "", - "name-service-name-operator": "", - "name-show-matches-only": "", - "name-span-name": "", - "name-span-name-operator": "", - "name-tags": "", + "category-span-filters": "スパンフィルター", + "name-critical-path-only": "クリティカルパスのみ表示", + "name-find-in-trace": "トレースで検索", + "name-max-duration": "最大継続時間", + "name-min-duration": "最小継続時間", + "name-service-name": "サービス名", + "name-service-name-operator": "サービス名の演算子", + "name-show-matches-only": "一致するもののみを表示", + "name-span-name": "スパン名", + "name-span-name-operator": "スパン名の演算子", + "name-tags": "タグ", "traces-panel": { "no-data-found-in-response": "応答にデータが見つかりません" } @@ -12204,20 +12785,20 @@ "axis-editor": { "log-mode-options": { "description": { - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-size": "サイズに基づいてバケットを分割" }, "label": { - "split": "" + "split": "分割" } }, "mode-options": { "description": { - "split-the-buckets-based-on-count": "", - "split-the-buckets-based-on-size": "" + "split-the-buckets-based-on-count": "カウントに基づいてバケットを分割", + "split-the-buckets-based-on-size": "サイズに基づいてバケットを分割" }, "label": { - "count": "", - "size": "" + "count": "カウント", + "size": "サイズ" } }, "placeholder-auto": "自動" @@ -12249,9 +12830,9 @@ }, "calculate-heatmap": { "add-heatmap-calculation-options": { - "name-x-bucket": "", - "name-y-bucket": "", - "name-y-bucket-scale": "" + "name-x-bucket": "Xバケット", + "name-y-bucket": "Yバケット", + "name-y-bucket-scale": "Yバケットスケール" } }, "concatenate-transformer-editor": { @@ -12312,8 +12893,8 @@ "tooltip-enable": "有効にする" }, "draggable-sort-order": { - "asc": "", - "desc": "", + "asc": "昇順", + "desc": "降順", "off": "オフ" }, "editors": { @@ -12712,9 +13293,9 @@ "description": { "reorder-hide-or-rename-fields": "" }, - "field-order": "", + "field-order": "フィールド順序", "field-order-auto": "自動", - "field-order-manual": "", + "field-order-manual": "手動", "first-frame-warning": "フィールドの整理は単一フレームでのみ機能します。最初に変換の結合を適用するか、入力をフィルタリングすることを検討してください。", "name": { "organize-fields": "" @@ -12841,7 +13422,7 @@ "polynomial": "多項式" } }, - "regression": "", + "regression": "回帰", "tooltip-number-of-xy-points-to-predict": "予測するX、Yポイント数" }, "rename-by-regex-transformer": { @@ -12994,10 +13575,10 @@ } }, "trend": { - "category-x-axis": "", - "description-x-field": "", - "name-x-field": "", - "placeholder-x-field": "" + "category-x-axis": "X軸", + "description-x-field": "増加する数値", + "name-x-field": "Xフィールド", + "placeholder-x-field": "最初の数値" }, "upgrade-box": { "discovery-text": "Pro機能を発見しました!", @@ -13260,20 +13841,20 @@ } }, "xychart": { - "category-xychart": "", - "name-fill-opacity": "", - "name-line-style": "", - "name-line-width": "", - "name-max-point-size": "", - "name-min-point-size": "", - "name-point-shape": "", - "name-point-size": "", - "name-point-stroke-width": "", - "name-series-mapping": "", - "name-show": "", + "category-xychart": "XYチャート", + "name-fill-opacity": "塗りつぶし不透明度", + "name-line-style": "線のスタイル", + "name-line-width": "線の太さ", + "name-max-point-size": "最大ポイントサイズ", + "name-min-point-size": "最小ポイントサイズ", + "name-point-shape": "ポイント形状", + "name-point-size": "ポイントサイズ", + "name-point-stroke-width": "ポイントストローク幅", + "name-series-mapping": "系列の対応付け", + "name-show": "表示", "point-shape-options": { - "label-circle": "", - "label-square": "" + "label-circle": "円形", + "label-square": "四角形" }, "series-editor": { "add-series": "系列を追加", @@ -13285,18 +13866,18 @@ "label-y-field": "Yフィールド", "placeholder-all-frames": "すべてのフレーム", "placeholder-select-frame": "フレームを選択", - "placeholder-x-field": "", - "placeholder-y-field": "", + "placeholder-x-field": "各フレームの最初の数値または時間フィールド", + "placeholder-y-field": "各フレームの残りの数値フィールド", "tooltip-delete-series": "系列の削除" }, "series-mapping-options": { - "label-auto": "", - "label-manual": "" + "label-auto": "自動", + "label-manual": "手動" }, "show-options": { - "label-both": "", - "label-lines": "", - "label-points": "" + "label-both": "両方", + "label-lines": "線", + "label-points": "ポイント" } } } \ No newline at end of file diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 30c73540b5d..d7addbf12e7 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1545,7 +1546,8 @@ "title": "평가 그룹 편집" }, "group-loader": { - "group-load-failed": "{{ namespaceName }}의 {{ groupName }} 그룹에서 규칙 로딩 실패" + "group-load-failed": "{{ namespaceName }}의 {{ groupName }} 그룹에서 규칙 로딩 실패", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "그룹을 삭제하는 중입니다" @@ -2456,7 +2458,6 @@ }, "rule-list": { "configure-datasource": "구성", - "draft-new-rule": "새 규칙 초안 작성", "ds-error": { "title": "이 데이터 소스에 대한 규칙을 불러올 수 없음" }, @@ -2482,6 +2483,7 @@ "new-alert-rule": "새 경고 규칙", "new-datasource-recording-rule": "새 데이터 소스 기록 규칙", "new-grafana-recording-rule": "새 Grafana 기록 규칙", + "new-rule-for-export": "", "pagination": { "next-page": "더 보기..." }, @@ -2789,6 +2791,7 @@ }, "template-preview": { "aria-label-refresh-preview": "미리 보기 새로 고침", + "empty-state": "", "label-preview": "미리보기", "refresh": "새로 고침" }, @@ -3243,6 +3246,11 @@ "id-token-attribute-name-label": "ID 토큰 속성 이름", "login-attribute-path-description": "사용자 ID 토큰에서 사용자 로그인 조회에 사용할 JMESPath 표현식입니다.", "login-attribute-path-label": "로그인 속성 경로", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "OAuth2 앱의 페더레이션 ID 자격 증명의 관리 ID 클라이언트 ID입니다.", "managed-identity-client-id-label": "FIC 관리 ID 클라이언트 ID", "name-attribute-path-description": "사용자 ID 토큰에서 사용자 이름 조회에 사용할 JMESPath 표현식입니다.이 이름은 사용자의 표시 이름으로 사용됩니다.", @@ -3341,6 +3349,9 @@ "text-badge-enabled": "활성화됨", "text-badge-not-enabled": "활성화되지 않음" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "사용하는 IdP의 .well-known/openid-configuration 엔드포인트", "title-open-id-connect-discovery-url": "OpenID Connect Discovery URL" @@ -3458,7 +3469,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "취소", "cannot-move-folders": "폴더는 이동할 수 없습니다.", "confirmation-text": "삭제", @@ -3491,6 +3501,23 @@ "browse-view": { "this-folder-is-empty": "이 폴더는 비어 있습니다" }, + "bulk-action-resources-form": { + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_other": "경고 규칙 {{count}}개", "dashboard_other": "대시보드 {{count}}개", @@ -3536,6 +3563,7 @@ "empty-message": "폴더를 찾을 수 없음", "error-title": "폴더 로딩 중 오류 발생", "non-folder-item": "비폴더 {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "폴더 검색", "unknown-error": "알 수 없는 오류" }, @@ -4519,6 +4547,10 @@ }, "edit-actions": { "add": "{{typeName}} 추가", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "패널 배경 변경", "panel-description": "패널 설명 변경", @@ -4533,6 +4565,7 @@ "edit-pane": { "elements": { "dashboard": "대시보드", + "element": "", "local-variable": "로컬 변수", "multiple-elements": "여러 요소", "multiple-elements-delete-text": "정말 이 요소를 삭제하시겠어요?", @@ -5740,7 +5773,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5906,8 +5938,8 @@ "title-someone-else-has-updated-this-dashboard": "다른 사람이 이 대시보드를 업데이트했습니다", "would-still-dashboard": "그래도 이 대시보드를 저장하시겠어요?" }, - "schema-v2-library-panels-export": "동적 대시보드 기능은 실험 단계이며 현재 대시보드 동작과 모든 기능이 동일하지는 않습니다. 이 기능은 라이브러리 패널을 지원하지 않는 새로운 스키마 형식을 기반으로 합니다. 즉, 다른 인스턴스에서 사용하기 위해 대시보드를 내보낼 때 라이브러리 패널이 포함되지 않는다는 것을 의미합니다. Grafana는 기능 <2>수명 주기가 진행됨에 따라 이를 지원할 계획입니다.", - "schema-v2-library-panels-export-title": "대시보드 스키마 V2는 아직 다른 인스턴스에서 사용할 라이브러리 패널 내보내기를 지원하지 않습니다", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "대시보드가 되돌릴 수 없게 변경됨" }, "save-dashboard-form-common-options": { @@ -7110,6 +7142,7 @@ "start-time": "시작 시간:" } }, + "share-span": "", "warnings": "경고" }, "span-filters": { @@ -7689,6 +7722,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7928,6 +8364,60 @@ "previous-label": "지난 달", "select-placeholder": "날짜/시간 선택" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "닫기" }, @@ -7975,6 +8465,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8079,7 +8583,10 @@ "no-data": "" }, "no-values-label": "값 없음", - "pagination-summary": "{{numRows}}개의 행 중에서 {{itemsRangeStart}}~{{displayedEnd}}" + "pagination-summary": "{{numRows}}개의 행 중에서 {{itemsRangeStart}}~{{displayedEnd}}", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "태그" @@ -8804,8 +9311,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8820,6 +9330,9 @@ "copy-log": "로그 라인 복사", "hide-details": "로그 세부 정보 표시", "icon-label": "로그 메뉴", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "로그 고정", "show-context": "맥락 보기", "show-details": "로그 세부 정보 숨기기", @@ -9653,7 +10166,9 @@ "aria-label": "도움말" }, "invite-user": { - "invite-new-member-button": "새 멤버 초대" + "invite-button": "초대", + "invite-new-member-button": "새 멤버 초대", + "invite-tooltip": "" }, "item": { "add-bookmark": "북마크에 추가", @@ -10408,17 +10923,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "이 기능은 현재 적극적으로 개발 중입니다. 최상의 경험과 최신 개선 사항 적용을 위해 Grafana의 <2>야간 빌드를 사용하는 것이 좋습니다." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "이 리포지토리 연결에 대해 알기 쉬운 이름 추가", "empty": "비어 있음", @@ -10428,7 +10965,9 @@ "label-display-name": "표시 이름", "placeholder-my-repository-connection": "내 리포지토리 연결", "text-loading-resource-information": "리소스 정보 로딩 중...", - "files-count_other": "파일 {{count}}개" + "dashboards-count_other": "", + "files-count_other": "파일 {{count}}개", + "folders-count_other": "" }, "check-repository": { "check": "확인" @@ -10439,36 +10978,20 @@ "button-save": "저장", "button-saving": "저장 중...", "description-enabled": "자동 풀링이 활성화되면 대상을 변경할 수 없습니다.", - "description-path": "Git 리포지토리의 하위 디렉터리 경로", "description-read-only": "Grafana를 통해 리소스를 수정할 수 없습니다.", - "description-repository-url": "GitHub 리포지토리 URL을 입력합니다", "description-title": "사람이 읽을 수 있는 구성 이름", "error-required": "이 필드는 필수 입력 항목입니다.", "error-save-repository": "리포지토리 설정 저장 실패", - "error-valid-github-url": "유효한 GitHub 리포지토리 URL을 입력하세요", "label-automatic-pulling": "자동 풀링", - "label-branch": "브랜치", "label-enabled": "활성화됨", - "label-github-token": "GitHub 토큰", "label-interval-seconds": "간격(초)", - "label-local-path": "로컬 경로", - "label-path": "경로", - "label-pr-workflow": "저장 시 풀 요청 옵션 활성화", "label-repository-type": "리포지토리 유형", - "label-repository-url": "리포지토리 URL", "label-target": "대상", "label-title": "제목", "option-entire-instance": "전체 인스턴스", - "option-github": "GitHub", - "option-local": "로컬", "option-managed-folder": "관리형 폴더", - "placeholder-branch": "메인", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "내 구성", - "placeholder-select-repository-type": "리포지토리 유형 선택" + "placeholder-my-config": "내 구성" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>웹훅을 구성하면 변경 사항이 커밋되는 즉시 Grafana에서 업데이트를 받을 수 있습니다. 변경 사항이 실제로 적용되기 전에 풀 요청을 사용하여 검토하고 승인하세요.", @@ -10482,27 +11005,8 @@ }, "connect-repository-button": { "configure": "구성", - "configure-file": "파일 프로비저닝 구성", - "configure-git-sync": "Git 동기화 구성", "repository-limit-info-alert": "리포지토리 한도 도달({{count}}개)" }, - "connect-step": { - "description-branch": "GitHub 리포지토리에 사용할 브랜치", - "description-github-path": "이것은 대시보드가 저장되고 프로비저닝될 GitHub 리포지토리의 하위 디렉터리에 대한 경로입니다", - "description-paste-your-git-hub-personal-access-token": "GitHub 개인 액세스 토큰을 붙여 넣으세요", - "description-repository-url": "GitHub 리포지토리의 URL을 붙여 넣으세요", - "error-field-required": "이 필드는 필수 입력 항목입니다.", - "error-invalid-github-url": "유효한 GitHub 리포지토리 URL을 입력하세요", - "label-access-token": "GitHub 액세스 토큰", - "label-branch": "브랜치 이름", - "label-local-path": "로컬 경로", - "label-path": "리포지토리의 하위 디렉터리 경로", - "label-repository-url": "GitHub 리포지토리 URL", - "placeholder-branch": "메인", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "삭제", "confirm-delete-repository": "정말 리포지토리 구성을 삭제하시겠어요?", @@ -10534,17 +11038,17 @@ "title-instant-updates-requests-webhooks": "웹훅을 통한 즉시 업데이트 및 풀 요청.", "title-visual-previews-in-pull-requests": "이미지 렌더링을 통해 풀 요청을 시각적으로 미리 보기" }, + "error-title-default": "", "expanded-row": { "job-specification": "작업 사양", - "summary": "요약", - "title-error": "오류" + "summary": "요약" }, "features-list": { "actions": { "set-up-required-feature-toggles": "필수 기능 토글 설정" }, - "manage-dashboards-provision-updates-automatically": "GitHub에서 코드로 대시보드를 관리하고 업데이트를 자동으로 프로비저닝합니다", - "manage-your-dashboards-with-remote-provisioning": "GitSync 시작하기", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "버전이 관리되는 스토리지에 대시보드를 저장하여 조직 및 기록 추적 기능 향상" }, "file-history-page": { @@ -10565,18 +11069,16 @@ }, "finish-step": { "description-enable-previews": "풀 요청에서 대시보드 변경 사항의 이미지 미리 보기를 추가합니다. Grafana 대시보드의 이미지는 Git 리포지토리에서 공유되며 리포지토리 액세스 권한이 있는 모든 사용자가 볼 수 있습니다.", + "description-generate-dashboard-previews": "", "description-image-rendering": "이미지 렌더링이 필요합니다. <2>이미지 렌더링 설정", - "description-often-shall-instance-updates-git-hub": "인스턴스가 GitHub에서 업데이트를 가져오는 빈도를 어떻게 설정할까요?", - "description-pr-enable-description": "사용자가 변경 사항을 저장할 때 풀 요청을 열 것인지 여부를 선택할 수 있습니다. 리포지토리에서 메인 브랜치에 대한 직접 변경을 허용하지 않는 경우에도 풀 요청이 필요할 수 있습니다.", + "description-preview-requirements": "", "description-read-only": "Grafana를 통해 리소스를 수정할 수 없습니다.", - "description-webhooks-enable": "사용자가 변경 사항을 저장할 때 풀 요청을 열 것인지 여부를 선택할 수 있습니다. 리포지토리에서 메인 브랜치에 대한 직접 변경을 허용하지 않는 경우에도 풀 요청이 필요할 수 있습니다.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "풀 요청에서 대시보드 미리 보기 활성화", - "label-pr-workflow": "저장 시 풀 요청 옵션 활성화", + "label-generate-dashboard-previews": "", "label-read-only": "읽기 전용", - "label-update-instance-interval-seconds": "인스턴스 간격 업데이트(초)", - "placeholder": "60", - "text-setup-later": "나중에 언제든지 설정할 수 있습니다", - "title-enhance-github": "GitHub 경험을 향상하세요" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "쿼리와 일치하는 결과가 없습니다", @@ -10607,6 +11109,57 @@ "header": "프로비저닝", "subtitle-provisioning-feature": "프로비저닝 연결 보기 및 관리" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "찾을 수 없음" }, @@ -10633,9 +11186,17 @@ "starting": "시작하는 중...", "status": { "title-error-running-job": "작업 실행 중 오류 발생", - "title-job-completed-successfully": "작업이 성공적으로 완료되었습니다" + "title-warning-running-job": "" }, - "summary": "요약" + "summary": "요약", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10661,7 +11222,7 @@ }, "repository-actions": { "settings": "설정", - "source-code": "소스 코드" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10726,6 +11287,24 @@ "title-legacy-storage": "레거시 스토리지", "title-queued-for-deletion": "삭제 대기열에 추가됨" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "베이스", "dashboard-preview": "대시보드 미리 보기", @@ -10755,6 +11334,11 @@ "label-current-step": "현재 단계", "label-pending-step": "보류 중인 단계" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "구성 편집", "button-edit": "편집", @@ -10765,9 +11349,6 @@ "tooltip-unhealthy-repository": "상태가 좋지 않은 리포지토리를 가져올 수 없습니다" }, "synchronize-step": { - "error-no-job-id": "작업 시작 실패", - "error-no-repository-name": "제공된 리포지토리 이름 없음", - "error-starting-job": "작업 시작 중 오류 발생", "synchronization-description": "각 과거 값에 대한 커밋을 포함합니다", "synchronization-options": "동기화 옵션" }, @@ -10776,6 +11357,7 @@ "go-to": "다음으로 이동", "make-sure": "반드시 이러한 권한을 포함하세요" }, + "warning-title-default": "", "wizard": { "alert-point-1": "이 프로세스 중에는 리소스를 생성, 편집 또는 삭제할 수 없습니다. 마지막 단계에서는 리소스가 표시되지 않습니다.", "alert-point-2": "프로비저닝이 완료되면 리소스가 다시 표시되고 외부 스토리지를 통해 관리됩니다.", @@ -11837,7 +12419,6 @@ }, "label-apply-to-entire-row": "전체 행에 적용", "label-background-display-mode": "배경 표시 모드", - "label-wrap-text": "자동 줄바꿈", "wrap-text": "자동 줄바꿈" }, "column-alignment-options": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 11d135e3e41..d4affb86dc3 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Evaluatiegroep bewerken" }, "group-loader": { - "group-load-failed": "Kan regels uit groep {{ groupName }} in {{ namespaceName }} niet laden" + "group-load-failed": "Kan regels uit groep {{ groupName }} in {{ namespaceName }} niet laden", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "De groep wordt verwijderd" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configureren", - "draft-new-rule": "Een nieuwe regel opstellen", "ds-error": { "title": "Kan geen regels laden voor deze gegevensbron" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nieuwe waarschuwingsregel", "new-datasource-recording-rule": "Nieuwe gegevensbron voor opnameregel", "new-grafana-recording-rule": "Nieuwe Grafana-opnameregel", + "new-rule-for-export": "", "pagination": { "next-page": "Meer weergeven..." }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Voorbeeld vernieuwen", + "empty-state": "", "label-preview": "Voorbeeld", "refresh": "Vernieuwen" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Naam id-tokenattribuut", "login-attribute-path-description": "JMESPath-expressie om te gebruiken voor het opzoeken van gebruikersaanmeldingen uit het gebruikers-id-token.", "login-attribute-path-label": "Pad inlogattribuut", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "De beheerde identiteitsclient-id van de federatieve identiteitsreferentie van je OAuth2-app.", "managed-identity-client-id-label": "FIC-beheerde identiteitsclient-id", "name-attribute-path-description": "JMESPath-expressie om te gebruiken voor het opzoeken van gebruikersaanmeldingen uit het gebruikers-id-token.\nDeze naam wordt gebruikt als de weergavenaam van de gebruiker.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Ingeschakeld", "text-badge-not-enabled": "Niet ingeschakeld" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Het .well-known/openid-configuration endpoint voor je IdP", "title-open-id-connect-discovery-url": "URL van OpenID Connect Discovery" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Annuleren", "cannot-move-folders": "Mappen kunnen niet worden verplaatst", "confirmation-text": "Verwijderen", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Deze map is leeg" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} waarschuwingsregel", "alertRule_other": "{{count}} waarschuwingsregel", @@ -3554,6 +3582,7 @@ "empty-message": "Geen mappen gevonden", "error-title": "Fout bij laden van mappen", "non-folder-item": "Niet-map {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Mappen doorzoeken", "unknown-error": "Onbekende fout" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "{{typeName}} toevoegen", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "{{typeName}} verplaatsen ", "panel-background": "Paneelachtergrond wijzigen", "panel-description": "Paneelbeschrijving wijzigen", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "element": "", "local-variable": "Lokale variabele", "multiple-elements": "Meerdere elementen", "multiple-elements-delete-text": "Weet je zeker dat je deze elementen wilt verwijderen?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Iemand anders heeft dit dashboard bijgewerkt", "would-still-dashboard": "Wil je dit dashboard nog steeds opslaan?" }, - "schema-v2-library-panels-export": "De dynamische dashboardfunctionaliteit is experimenteel en heeft geen volledige functiepariteit met het huidige gedrag van dashboards. Het is gebaseerd op een nieuw schemaformaat, dat geen bibliotheekpanelen ondersteunt. Dit betekent dat we bij het exporteren van het dashboard om het in een andere instantie te gebruiken, geen bibliotheekpanelen opnemen. We zijn van plan om ze te ondersteunen naarmate we verder komen in de <2>levenscyclus van de functie.", - "schema-v2-library-panels-export-title": "Dashboardschema V2 biedt nog geen ondersteuning voor het exporteren van bibliotheekpanelen die in een andere instantie kunnen worden gebruikt", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Dashboard onomkeerbaar gewijzigd" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Starttijd:" } }, + "share-span": "", "warnings": "Waarschuwingen" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Vorige maand", "select-placeholder": "Selecteer datum/tijd" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Sluiten" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "Veld selecteren" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "Velden met naam die overeenkomen met regex", "name-fields-by-query": "Velden opgehaald via de query", "name-fields-with-name": "Velden met naam", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Geen waarden", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} van {{numRows}} rijen" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} van {{numRows}} rijen", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tags" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "Weer te geven velden.", + "open-assistant": "", "pin-line": "Logboek vastzetten", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "Geen resultaten om weer te geven." }, @@ -8849,6 +9360,9 @@ "copy-log": "Logboekregel kopiëren", "hide-details": "Logboekdetails weergeven", "icon-label": "Logboekmenu", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Logboek vastzetten", "show-context": "Context tonen", "show-details": "Logboekdetails verbergen", @@ -9682,7 +10196,9 @@ "aria-label": "Help" }, "invite-user": { - "invite-new-member-button": "Nieuw lid uitnodigen" + "invite-button": "Uitnodigen", + "invite-new-member-button": "Nieuw lid uitnodigen", + "invite-tooltip": "" }, "item": { "add-bookmark": "Toevoegen aan bladwijzers", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Er wordt momenteel actief gewerkt aan de ontwikkeling van deze functie. Voor de beste ervaring en de nieuwste verbeteringen raden we je aan om de <2>nachtelijke versie van Grafana te gebruiken." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Voeg een duidelijke naam toe voor deze repository-verbinding", "empty": "Leeg", @@ -10460,8 +10998,12 @@ "label-display-name": "Weergavenaam", "placeholder-my-repository-connection": "Mijn repository-verbinding", "text-loading-resource-information": "Broninformatie laden...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} bestanden", - "files-count_other": "{{count}} bestanden" + "files-count_other": "{{count}} bestanden", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Controleer" @@ -10472,36 +11014,20 @@ "button-save": "Opslaan", "button-saving": "Aan het opslaan ...", "description-enabled": "Zodra automatische pulling is ingeschakeld, kan het doel niet worden gewijzigd.", - "description-path": "Pad naar een submap in de Git-repository", "description-read-only": "Bronnen kunnen niet worden gewijzigd via Grafana.", - "description-repository-url": "Voer de GitHub-repository-URL in", "description-title": "Een voor mensen leesbare naam voor de configuratie", "error-required": "Dit veld is verplicht.", "error-save-repository": "Kan de repository-instellingen niet opslaan", - "error-valid-github-url": "Voer een geldige GitHub-repository-URL in", "label-automatic-pulling": "Automatische pulling", - "label-branch": "Vestiging", "label-enabled": "Ingeschakeld", - "label-github-token": "GitHub-token", "label-interval-seconds": "Interval (seconden)", - "label-local-path": "Lokaal pad", - "label-path": "Path", - "label-pr-workflow": "Pull request-optie inschakelen bij opslaan", "label-repository-type": "Type repository", - "label-repository-url": "Repository-URL", "label-target": "Doel", "label-title": "Titel", "option-entire-instance": "Gehele instantie", - "option-github": "GitHub", - "option-local": "Lokaal", "option-managed-folder": "Beheerde map", - "placeholder-branch": "voornaamste", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Mijn configuratie", - "placeholder-select-repository-type": "Type repository selecteren" + "placeholder-my-config": "Mijn configuratie" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configureer webhooks om directe updates in Grafana te krijgen zodra wijzigingen zijn doorgevoerd. Bekijk en keur wijzigingen goed met pull requests voordat ze live gaan.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configureren", - "configure-file": "Bestandsprovisioning configureren", - "configure-git-sync": "Git-synchronisatie configureren", "repository-limit-info-alert": "Limiet van repository bereikt ({{count}})" }, - "connect-step": { - "description-branch": "Te gebruiken tak voor de GitHub-repository", - "description-github-path": "Dit is het pad naar een submap in je GitHub-repository waar dashboards worden opgeslagen en ingericht", - "description-paste-your-git-hub-personal-access-token": "Plak je persoonlijke GitHub-toegangstoken", - "description-repository-url": "Plak de URL van je GitHub-repository", - "error-field-required": "Dit veld is verplicht.", - "error-invalid-github-url": "Voer een geldige GitHub-repository-URL in", - "label-access-token": "GitHub-toegangstoken", - "label-branch": "Taknaam", - "label-local-path": "Lokaal pad", - "label-path": "Pad naar submap in repository", - "label-repository-url": "GitHub-repository-link", - "placeholder-branch": "voornaamste", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Verwijderen", "confirm-delete-repository": "Weet je zeker dat je de repository-configuratie wilt verwijderen?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Directe updates en pull requests met webhooks.", "title-visual-previews-in-pull-requests": "Visuele voorbeelden in pull requests met beeldweergave" }, + "error-title-default": "", "expanded-row": { "job-specification": "Taakbeschrijving", - "summary": "Samenvatting", - "title-error": "Fout" + "summary": "Samenvatting" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Vereiste functieschakelaars instellen" }, - "manage-dashboards-provision-updates-automatically": "Beheer dashboards als code in GitHub en verstrek updates automatisch", - "manage-your-dashboards-with-remote-provisioning": "Aan de slag met GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Sla dashboards op in versiegecontroleerde opslag voor betere organisatie en het bijhouden van de geschiedenis" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Voegt een afbeeldingsvoorbeeld toe van dashboardwijzigingen in pull requests. Afbeeldingen van je Grafana-dashboards worden gedeeld in je Git-repository en zijn zichtbaar voor iedereen met toegang tot de repository.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Vereist beeldweergave. <2>Beeldweergave instellen", - "description-often-shall-instance-updates-git-hub": "Hoe vaak moet de instantie updates van GitHub ophalen?", - "description-pr-enable-description": "Stelt gebruikers in staat om te kiezen of ze een pull request willen openen bij het opslaan van wijzigingen. Als de repository geen directe wijzigingen in de hoofdtak toestaat, kan er nog steeds een pull request nodig zijn.", + "description-preview-requirements": "", "description-read-only": "Bronnen kunnen niet worden gewijzigd via Grafana.", - "description-webhooks-enable": "Stelt gebruikers in staat om te kiezen of ze een pull request willen openen bij het opslaan van wijzigingen. Als de repository geen directe wijzigingen in de hoofdtak toestaat, kan er nog steeds een pull request nodig zijn.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Dashboardvoorbeelden inschakelen in pull requests", - "label-pr-workflow": "Pull request-optie inschakelen bij opslaan", + "label-generate-dashboard-previews": "", "label-read-only": "Alleen lezen", - "label-update-instance-interval-seconds": "Instantie-interval bijwerken (seconden)", - "placeholder": "60", - "text-setup-later": "Je kunt dit altijd later instellen", - "title-enhance-github": "Verbeter je GitHub-ervaring" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Geen resultaten die overeenkomen met je query", @@ -10640,6 +11145,57 @@ "header": "Provisioning", "subtitle-provisioning-feature": "Je provisioningverbindingen bekijken en beheren" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Niet gevonden" }, @@ -10666,9 +11222,17 @@ "starting": "Starten...", "status": { "title-error-running-job": "Er is een fout opgetreden bij het uitvoeren van de taak", - "title-job-completed-successfully": "Taak is voltooid" + "title-warning-running-job": "" }, - "summary": "Samenvatting" + "summary": "Samenvatting", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Instellingen", - "source-code": "Broncode" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Legacy-opslag", "title-queued-for-deletion": "In de wachtrij voor verwijdering" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Basis", "dashboard-preview": "Dashboardvoorbeeld", @@ -10788,6 +11370,11 @@ "label-current-step": "Huidige stap", "label-pending-step": "Stap in afwachting" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "De configuratie bewerken", "button-edit": "Bewerken", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Kan geen ongezonde repository ophalen" }, "synchronize-step": { - "error-no-job-id": "Kan taak niet starten", - "error-no-repository-name": "Geen repository-naam opgegeven", - "error-starting-job": "Er is een fout opgetreden bij het starten van de taak", "synchronization-description": "Commits opnemen voor elke historische waarde", "synchronization-options": "Synchronisatie-opties" }, @@ -10809,6 +11393,7 @@ "go-to": "Ga naar", "make-sure": "Zorg ervoor dat je deze toestemmingen opneemt" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Bronnen kunnen tijdens dit proces niet worden gemaakt, bewerkt of verwijderd. In de laatste stap verdwijnen ze.", "alert-point-2": "Zodra de provisioning is voltooid, verschijnen bronnen weer en worden ze beheerd via externe opslag.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Toepassen op hele rij", "label-background-display-mode": "Achtergrondweergavemodus", - "label-wrap-text": "Tekst afbreken", "wrap-text": "Tekst afbreken" }, "column-alignment-options": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 673833e2fbe..121c28f0b17 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1563,7 +1564,8 @@ "title": "Edytuj grupę ocen" }, "group-loader": { - "group-load-failed": "Nie udało się wczytać reguł z grupy {{ groupName }} w przestrzeni nazw {{ namespaceName }}" + "group-load-failed": "Nie udało się wczytać reguł z grupy {{ groupName }} w przestrzeni nazw {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Trwa usuwanie grupy" @@ -2486,7 +2488,6 @@ }, "rule-list": { "configure-datasource": "Konfiguruj", - "draft-new-rule": "Zaprojektuj nową regułę", "ds-error": { "title": "Nie można załadować reguł dla tego źródła danych" }, @@ -2512,6 +2513,7 @@ "new-alert-rule": "Nowa reguła alertu", "new-datasource-recording-rule": "Nowa reguła rejestracji źródła danych", "new-grafana-recording-rule": "Nowa reguła rejestracji w usłudze Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Pokaż więcej…" }, @@ -2828,6 +2830,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Odśwież podgląd", + "empty-state": "", "label-preview": "Podgląd", "refresh": "Odśwież" }, @@ -3282,6 +3285,11 @@ "id-token-attribute-name-label": "Nazwa atrybutu tokena identyfikatora", "login-attribute-path-description": "Wyrażenie JMESPath używane do wyszukiwania loginu na podstawie tokena identyfikatora użytkownika.", "login-attribute-path-label": "Ścieżka atrybutu loginu", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "Identyfikator klienta tożsamości zarządzanej w poświadczeniach tożsamości federacyjnej Twojej aplikacji OAuth2.", "managed-identity-client-id-label": "Identyfikator klienta tożsamości zarządzanej FIC", "name-attribute-path-description": "Wyrażenie JMESPath używane do wyszukiwania nazwy użytkownika na podstawie tokena identyfikatora użytkownika. \nTa nazwa będzie używana jako nazwa wyświetlana użytkownika.", @@ -3380,6 +3388,9 @@ "text-badge-enabled": "Włączone", "text-badge-not-enabled": "Niewłączone" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Punkt końcowy konfiguracji .well-known/openid-configuration dla Twojego dostawcy tożsamości", "title-open-id-connect-discovery-url": "Adres URL wykrywania OpenID Connect" @@ -3497,7 +3508,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Anuluj", "cannot-move-folders": "Nie można przenosić folderów", "confirmation-text": "Usuń", @@ -3530,6 +3540,26 @@ "browse-view": { "this-folder-is-empty": "Ten folder jest pusty" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_few": "", + "failed-alert_many": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} reguła alertu", "alertRule_few": "{{count}} reguły alertu", @@ -3590,6 +3620,7 @@ "empty-message": "Nie znaleziono folderów", "error-title": "Błąd podczas ładowania folderów", "non-folder-item": "Element {{itemKind}} {{itemUID}} poza folderem", + "root-title": "", "search-placeholder": "Przeszukaj foldery", "unknown-error": "Nieznany błąd" }, @@ -4573,6 +4604,10 @@ }, "edit-actions": { "add": "Dodaj: {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Zmień tło panelu", "panel-description": "Zmień opis panelu", @@ -4587,6 +4622,7 @@ "edit-pane": { "elements": { "dashboard": "Pulpit", + "element": "", "local-variable": "Zmienna lokalna", "multiple-elements": "Wiele elementów", "multiple-elements-delete-text": "Czy na pewno chcesz usunąć te elementy?", @@ -5800,7 +5836,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5966,8 +6001,8 @@ "title-someone-else-has-updated-this-dashboard": "Ktoś inny zaktualizował ten pulpit", "would-still-dashboard": "Czy nadal chcesz zapisać ten pulpit?" }, - "schema-v2-library-panels-export": "Funkcja pulpitu dynamicznego jest eksperymentalna i nie odpowiada w pełni obecnemu sposobowi działania pulpitów. Opiera się na nowym formacie schematu, który nie obsługuje paneli biblioteki. Oznacza to, że przy eksporcie pulpitu, aby użyć go w innej instancji, nie uwzględnimy paneli biblioteki. Zamierzamy dodać obsługę tych paneli w miarę <2>rozwoju tej funkcji.", - "schema-v2-library-panels-export-title": "Schemat pulpitu w wersji 2 nie obsługuje jeszcze eksportowania paneli biblioteki do użytku w innej instancji", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Pulpit został nieodwracalnie zmieniony" }, "save-dashboard-form-common-options": { @@ -7173,6 +7208,7 @@ "start-time": "Czas rozpoczęcia:" } }, + "share-span": "", "warnings": "Ostrzeżenia" }, "span-filters": { @@ -7752,6 +7788,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7991,6 +8430,60 @@ "previous-label": "Poprzedni miesiąc", "select-placeholder": "Wybierz datę/godzinę" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Zamknij" }, @@ -8038,6 +8531,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8142,7 +8649,10 @@ "no-data": "" }, "no-values-label": "Brak wartości", - "pagination-summary": "{{itemsRangeStart}}–{{displayedEnd}} z {{numRows}} wierszy" + "pagination-summary": "{{itemsRangeStart}}–{{displayedEnd}} z {{numRows}} wierszy", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Znaczniki" @@ -8891,8 +9401,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8907,6 +9420,9 @@ "copy-log": "Kopiuj wiersz logu", "hide-details": "Pokaż szczegóły dziennika", "icon-label": "Menu logu", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Przypnij log", "show-context": "Pokaż kontekst", "show-details": "Ukryj szczegóły dziennika", @@ -9740,7 +10256,9 @@ "aria-label": "Pomoc" }, "invite-user": { - "invite-new-member-button": "Zaproś nowego członka" + "invite-button": "Zaproś", + "invite-new-member-button": "Zaproś nowego członka", + "invite-tooltip": "" }, "item": { "add-bookmark": "Dodaj do zakładek", @@ -10504,17 +11022,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Ta funkcja jest obecnie aktywnie rozwijana. Aby uzyskać najlepsze wrażenia i najnowsze ulepszenia, zalecamy korzystanie z <2>nocnej kompilacji aplikacji Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Dodaj zrozumiałą nazwę tego połączenia z repozytorium", "empty": "Pusty", @@ -10524,10 +11064,18 @@ "label-display-name": "Nazwa wyświetlana", "placeholder-my-repository-connection": "Połączenie z moim repozytorium", "text-loading-resource-information": "Wczytywanie informacji o zasobach…", + "dashboards-count_one": "", + "dashboards-count_few": "", + "dashboards-count_many": "", + "dashboards-count_other": "", "files-count_one": "{{count}} plik", "files-count_few": "{{count}} pliki", "files-count_many": "{{count}} plików", - "files-count_other": "{{count}} pliku" + "files-count_other": "{{count}} pliku", + "folders-count_one": "", + "folders-count_few": "", + "folders-count_many": "", + "folders-count_other": "" }, "check-repository": { "check": "Sprawdźmy." @@ -10538,36 +11086,20 @@ "button-save": "Zapisz", "button-saving": "Zapisywanie…", "description-enabled": "Po włączeniu automatycznego pobierania (pull) nie można zmienić celu.", - "description-path": "Ścieżka do podkatalogu w repozytorium Git", "description-read-only": "Zasobów nie można modyfikować za pośrednictwem usługi Grafana.", - "description-repository-url": "Podaj adres URL repozytorium GitHub", "description-title": "Czytelna dla człowieka nazwa konfiguracji", "error-required": "To pole jest wymagane.", "error-save-repository": "Nie udało się zapisać ustawień repozytorium", - "error-valid-github-url": "Podaj prawidłowy adres URL repozytorium GitHub", "label-automatic-pulling": "Automatyczne operacje pull", - "label-branch": "Gałąź", "label-enabled": "Włączone", - "label-github-token": "Token GitHub", "label-interval-seconds": "Odstęp czasu (sekundy)", - "label-local-path": "Ścieżka lokalna", - "label-path": "Ścieżka", - "label-pr-workflow": "Włącz opcję żądania pull podczas zapisywania", "label-repository-type": "Typ repozytorium", - "label-repository-url": "Adres URL repozytorium", "label-target": "Cel", "label-title": "Tytuł", "option-entire-instance": "Cała instancja", - "option-github": "GitHub", - "option-local": "Lokalnie", "option-managed-folder": "Folder zarządzany", - "placeholder-branch": "main", - "placeholder-github-token": "ghp_Token1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/nazwa_użytkownika/nazwa-repozytorium", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/ścieżka/do/repozytorium", - "placeholder-my-config": "Moja konfiguracja", - "placeholder-select-repository-type": "Wybierz typ repozytorium" + "placeholder-my-config": "Moja konfiguracja" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Skonfiguruj webhooki, aby otrzymywać aktualizacje w usłudze Grafana natychmiast po zatwierdzeniu zmian (commit). Przejrzyj i zatwierdź zmiany za pomocą żądań pull przed ich opublikowaniem.", @@ -10581,27 +11113,8 @@ }, "connect-repository-button": { "configure": "Konfiguruj", - "configure-file": "Skonfiguruj aprowizację plików", - "configure-git-sync": "Konfiguruj synchronizację z usługą Git", "repository-limit-info-alert": "Osiągnięto limit repozytorium ({{count}})" }, - "connect-step": { - "description-branch": "Gałąź używana w repozytorium GitHub", - "description-github-path": "Jest to ścieżka do podkatalogu w repozytorium GitHub, w którym będą przechowywane i udostępniane pulpity nawigacyjne", - "description-paste-your-git-hub-personal-access-token": "Wklej osobisty token dostępu GitHub", - "description-repository-url": "Wklej adres URL repozytorium GitHub", - "error-field-required": "To pole jest wymagane.", - "error-invalid-github-url": "Podaj prawidłowy adres URL repozytorium GitHub", - "label-access-token": "Token dostępu GitHub", - "label-branch": "Nazwa gałęzi", - "label-local-path": "Ścieżka lokalna", - "label-path": "Ścieżka do podkatalogu w repozytorium", - "label-repository-url": "Adres URL repozytorium GitHub", - "placeholder-branch": "main", - "placeholder-github-token": "github_pat_Token1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/nazwa_użytkownika/repozytorium", - "placeholder-local-path": "/ścieżka/do/repozytorium" - }, "delete-repository-button": { "button-delete": "Usuń", "confirm-delete-repository": "Czy na pewno chcesz usunąć konfigurację repozytorium?", @@ -10633,17 +11146,17 @@ "title-instant-updates-requests-webhooks": "Błyskawiczne aktualizacje i żądania pull dzięki webhookom.", "title-visual-previews-in-pull-requests": "Podglądy wizualne w żądaniach pull z renderowaniem obrazów" }, + "error-title-default": "", "expanded-row": { "job-specification": "Specyfikacja zadania", - "summary": "Podsumowanie", - "title-error": "Błąd" + "summary": "Podsumowanie" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Skonfiguruj wymagane przełączniki funkcji" }, - "manage-dashboards-provision-updates-automatically": "Zarządzaj pulpitami jako kodem w GitHub i  automatycznie aprowizuj aktualizacje", - "manage-your-dashboards-with-remote-provisioning": "Rozpocznij pracę z funkcją GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Przechowuj pulpity nawigacyjne w pamięci z kontrolą wersji, aby zapewnić lepszą organizację i śledzenie historii zmian" }, "file-history-page": { @@ -10664,18 +11177,16 @@ }, "finish-step": { "description-enable-previews": "Dodaje podgląd obrazu zmian pulpitu w żądaniach pull. Obrazy pulpitów Grafany zostaną udostępnione w repozytorium Git i będą widoczne dla każdego, kto ma do niego dostęp.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Wymaga renderowania obrazów. <2>Skonfiguruj renderowanie obrazów", - "description-often-shall-instance-updates-git-hub": "Jak często instancja będzie pobierać aktualizacje z GitHub?", - "description-pr-enable-description": "Pozwala użytkownikom wybrać, czy chcą otworzyć żądanie pull podczas zapisywania zmian. Jeśli repozytorium nie zezwala na bezpośrednie zmiany w głównej gałęzi, nadal może być wymagane żądanie pull.", + "description-preview-requirements": "", "description-read-only": "Zasobów nie można modyfikować za pośrednictwem usługi Grafana.", - "description-webhooks-enable": "Pozwala użytkownikom wybrać, czy chcą otworzyć żądanie pull podczas zapisywania zmian. Jeśli repozytorium nie zezwala na bezpośrednie zmiany w głównej gałęzi, nadal może być wymagane żądanie pull.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Włącz podglądy pulpitu w żądaniach pull", - "label-pr-workflow": "Włącz opcję żądania pull podczas zapisywania", + "label-generate-dashboard-previews": "", "label-read-only": "Tylko do odczytu", - "label-update-instance-interval-seconds": "Odstęp czasu aktualizacji instancji (w sekundach)", - "placeholder": "60", - "text-setup-later": "Zawsze możesz to skonfigurować później", - "title-enhance-github": "Zwiększ wygodę korzystania z GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Brak wyników pasujących do zapytania", @@ -10706,6 +11217,57 @@ "header": "Konfiguracja", "subtitle-provisioning-feature": "Wyświetlaj połączenia aprowizacyjne i nimi zarządzaj" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Nie znaleziono" }, @@ -10732,9 +11294,17 @@ "starting": "Uruchamianie…", "status": { "title-error-running-job": "Błąd uruchamiania zadania", - "title-job-completed-successfully": "Zadanie zakończone pomyślnie" + "title-warning-running-job": "" }, - "summary": "Podsumowanie" + "summary": "Podsumowanie", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10760,7 +11330,7 @@ }, "repository-actions": { "settings": "Ustawienia", - "source-code": "Kod źródłowy" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10825,6 +11395,24 @@ "title-legacy-storage": "Starsza pamięć masowa", "title-queued-for-deletion": "Dodano do kolejki do usunięcia" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Podstawa", "dashboard-preview": "Podgląd pulpitu", @@ -10854,6 +11442,11 @@ "label-current-step": "Aktualny krok", "label-pending-step": "Oczekujący krok" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Edytuj konfigurację", "button-edit": "Edytuj", @@ -10864,9 +11457,6 @@ "tooltip-unhealthy-repository": "Nie można pobrać danych z niesprawnego repozytorium" }, "synchronize-step": { - "error-no-job-id": "Nie udało się uruchomić zadania", - "error-no-repository-name": "Nie wprowadzono nazwy repozytorium", - "error-starting-job": "Błąd uruchamiania zadania", "synchronization-description": "Uwzględnij zatwierdzenia dla każdej wartości historycznej", "synchronization-options": "Opcje synchronizacji" }, @@ -10875,6 +11465,7 @@ "go-to": "Przejdź do", "make-sure": "Pamiętaj, aby uwzględnić te uprawnienia" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Podczas tego procesu nie będzie można tworzyć, edytować ani usuwać zasobów. W ostatnim kroku zasoby znikną.", "alert-point-2": "Po zakończeniu aprowizacji zasoby pojawią się ponownie i będą zarządzane za pośrednictwem zewnętrznej pamięci masowej.", @@ -11954,7 +12545,6 @@ }, "label-apply-to-entire-row": "Zastosuj do całego wiersza", "label-background-display-mode": "Tryb wyświetlania tła", - "label-wrap-text": "Zawijaj tekst", "wrap-text": "Zawijaj tekst" }, "column-alignment-options": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index efe47b733fb..9a2163064f9 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Editar grupo de avaliação" }, "group-loader": { - "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}" + "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "O grupo está sendo excluído" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configuração", - "draft-new-rule": "Crie o rascunho de uma nova regra", "ds-error": { "title": "Não é possível carregar regras para esta fonte de dados" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nova regra de alerta", "new-datasource-recording-rule": "Nova regra de registro de fonte de dados", "new-grafana-recording-rule": "Nova regra de registro da Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Exibir mais…" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Atualizar a pré-visualização", + "empty-state": "", "label-preview": "Visualizar", "refresh": "Atualizar" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Nome do atributo do token de ID", "login-attribute-path-description": "Expressão JMESPath a ser usada para a consulta de login do usuário a partir do token de ID do usuário.", "login-attribute-path-label": "Caminho do atributo de login", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "O ID do cliente de identidade gerenciada da credencial de identidade federada (FIC) do seu aplicativo OAuth2.", "managed-identity-client-id-label": "ID do cliente de identidade gerenciada da FIC", "name-attribute-path-description": "Expressão JMESPath a ser usada para a consulta de nome de usuário a partir do token de ID do usuário. \nEste nome será usado como o nome de exibição do usuário.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Ativado", "text-badge-not-enabled": "Não ativado" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "O endpoint .well-known/openid-configuration para seu IdP", "title-open-id-connect-discovery-url": "URL de descoberta do OpenID Connect" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Cancelar", "cannot-move-folders": "As pastas não podem ser movidas", "confirmation-text": "Excluir", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Esta pasta está vazia" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} regra de alerta", "alertRule_other": "{{count}} regras de alerta", @@ -3554,6 +3582,7 @@ "empty-message": "Nenhuma pasta encontrada", "error-title": "Erro ao carregar pastas", "non-folder-item": "{{itemKind}} {{itemUID}} não é uma pasta", + "root-title": "", "search-placeholder": "Buscar pastas", "unknown-error": "Erro desconhecido" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "Adicionar {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Alterar plano de fundo do painel", "panel-description": "Alterar descrição do painel", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Painel de controle", + "element": "", "local-variable": "Variável local", "multiple-elements": "Vários elementos", "multiple-elements-delete-text": "Tem certeza de que deseja excluir estes elementos?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Outra pessoa atualizou este painel", "would-still-dashboard": "Deseja salvar este painel mesmo assim?" }, - "schema-v2-library-panels-export": "A funcionalidade de painel dinâmico está em testes e não possui todos os recursos presentes no comportamento atual dos painéis. Ele se baseia em um novo formato de esquema, que não aceita painéis da biblioteca. Isso significa que, ao exportar o painel para usá-lo em outra instância, não incluiremos painéis da biblioteca. Pretendemos aceitá-los à medida que avançamos no <2>ciclo de vida do recurso.", - "schema-v2-library-panels-export-title": "O esquema de painel V2 ainda não aceita a exportação de painéis da biblioteca para uso em outra instância", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Painel alterado de forma irreversível" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Horário de início:" } }, + "share-span": "", "warnings": "Avisos" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Mês anterior", "select-placeholder": "Selecione a data/hora" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Fechar" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Sem valores", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} de {{numRows}} linhas" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} de {{numRows}} linhas", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Tags" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Copiar linha do log", "hide-details": "Exibir detalhes do log", "icon-label": "Menu do log", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Fixar log", "show-context": "Exibir contexto", "show-details": "Ocultar detalhes do log", @@ -9682,7 +10196,9 @@ "aria-label": "Ajuda" }, "invite-user": { - "invite-new-member-button": "Convidar um novo membro" + "invite-button": "", + "invite-new-member-button": "Convidar um novo membro", + "invite-tooltip": "" }, "item": { "add-bookmark": "Adicionar aos Marcadores", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Este recurso está em desenvolvimento ativo no momento. Para ter uma melhor experiência e conferir as melhorias mais recentes, recomendamos usar a <2>compilação noturna da Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Adicione um nome de fácil compreensão para esta conexão de repositório", "empty": "Vazio", @@ -10460,8 +10998,12 @@ "label-display-name": "Nome de exibição", "placeholder-my-repository-connection": "Minha conexão de repositório", "text-loading-resource-information": "Carregando informações de recursos…", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} arquivos", - "files-count_other": "{{count}} arquivos" + "files-count_other": "{{count}} arquivos", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Verificar" @@ -10472,36 +11014,20 @@ "button-save": "Salvar", "button-saving": "Salvando...", "description-enabled": "Depois que a extração automática for ativada, não será possível alterar o destino.", - "description-path": "Caminho para um subdiretório no repositório Git", "description-read-only": "Os recursos não podem ser modificados por meio da Grafana.", - "description-repository-url": "Insira a URL do repositório do GitHub", "description-title": "Um nome de fácil compreensão para a configuração", "error-required": "Este campo é obrigatório.", "error-save-repository": "Falha ao salvar as configurações do repositório", - "error-valid-github-url": "Insira uma URL válida do repositório do GitHub", "label-automatic-pulling": "Extração automática", - "label-branch": "Branch", "label-enabled": "Ativado", - "label-github-token": "Token do GitHub", "label-interval-seconds": "Intervalo (segundos)", - "label-local-path": "Caminho local", - "label-path": "Caminho", - "label-pr-workflow": "Ativar a opção de solicitação de extração ao salvar", "label-repository-type": "Tipo de repositório", - "label-repository-url": "URL do repositório", "label-target": "Alvo", "label-title": "Título", "option-entire-instance": "Instância inteira", - "option-github": "GitHub", - "option-local": "Local", "option-managed-folder": "Pasta gerenciada", - "placeholder-branch": "principal", - "placeholder-github-token": "ghp_seuTokenAqui1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Minha configuração", - "placeholder-select-repository-type": "Selecione o tipo de repositório" + "placeholder-my-config": "Minha configuração" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configure webhooks para receber atualizações instantâneas na Grafana assim que as alterações forem confirmadas. Revise e aprove as alterações por meio de solicitações de extração antes que elas sejam implementadas.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configuração", - "configure-file": "Configurar o provisionamento de arquivos", - "configure-git-sync": "Configurar sincronização do Git", "repository-limit-info-alert": "Limite do repositório atingido ({{count}})" }, - "connect-step": { - "description-branch": "Branch que será usado para o repositório do GitHub", - "description-github-path": "Este é o caminho para um subdiretório no seu repositório do GitHub, a partir do qual os painéis serão armazenados e provisionados", - "description-paste-your-git-hub-personal-access-token": "Cole seu token de acesso pessoal do GitHub", - "description-repository-url": "Cole a URL do seu repositório do GitHub", - "error-field-required": "Este campo é obrigatório.", - "error-invalid-github-url": "Insira uma URL válida do repositório do GitHub", - "label-access-token": "Token de acesso do GitHub", - "label-branch": "Nome do branch", - "label-local-path": "Caminho local", - "label-path": "Caminho para o subdiretório no repositório", - "label-repository-url": "URL do repositório do GitHub", - "placeholder-branch": "principal", - "placeholder-github-token": "github_pat_seuTokenAqui1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Excluir", "confirm-delete-repository": "Tem certeza de que deseja excluir a configuração do repositório?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Atualizações imediatas e solicitações de extração com webhooks.", "title-visual-previews-in-pull-requests": "Pré-visualizações em solicitações de extração com renderização de imagens" }, + "error-title-default": "", "expanded-row": { "job-specification": "Especificação da tarefa", - "summary": "Resumo", - "title-error": "Erro" + "summary": "Resumo" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Configure os botões de alternância de recursos necessários" }, - "manage-dashboards-provision-updates-automatically": "Gerencie painéis como código no GitHub e provisione atualizações automaticamente", - "manage-your-dashboards-with-remote-provisioning": "Comece a usar o GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Armazene os painéis em um armazenamento com controle por versão para aprimorar a organização e o rastreamento de histórico" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Adiciona uma pré-visualização em imagem das alterações do painel nas solicitações de extração. As imagens dos seus painéis da Grafana serão compartilhadas no seu repositório Git e estarão disponíveis para qualquer pessoa com acesso ao repositório.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Requer renderização de imagem. <2>Configurar a renderização de imagens", - "description-often-shall-instance-updates-git-hub": "Com que frequência a instância deve buscar atualizações do GitHub?", - "description-pr-enable-description": "Permite que os usuários escolham se desejam abrir uma solicitação de extração ao salvar as alterações. Se o repositório não permitir alterações diretas no branch principal, ainda pode ser necessário realizar uma solicitação de extração.", + "description-preview-requirements": "", "description-read-only": "Os recursos não podem ser modificados por meio da Grafana.", - "description-webhooks-enable": "Permite que os usuários escolham se desejam abrir uma solicitação de extração ao salvar as alterações. Se o repositório não permitir alterações diretas no branch principal, ainda pode ser necessário realizar uma solicitação de extração.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Ativar pré-visualizações do painel em solicitações de extração", - "label-pr-workflow": "Ativar a opção de solicitação de extração ao salvar", + "label-generate-dashboard-previews": "", "label-read-only": "Apenas leitura", - "label-update-instance-interval-seconds": "Atualizar intervalo da instância (segundos)", - "placeholder": "60", - "text-setup-later": "Você pode configurar isso mais tarde", - "title-enhance-github": "Aprimore sua experiência no GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Nenhum resultado corresponde à sua consulta", @@ -10640,6 +11145,57 @@ "header": "Aprovisionamento", "subtitle-provisioning-feature": "Visualize e gerencie suas conexões de provisionamento" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Não encontrado" }, @@ -10666,9 +11222,17 @@ "starting": "Iniciando…", "status": { "title-error-running-job": "Erro ao executar a tarefa", - "title-job-completed-successfully": "Tarefa concluída com sucesso" + "title-warning-running-job": "" }, - "summary": "Resumo" + "summary": "Resumo", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Configurações", - "source-code": "Código-fonte" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Armazenamento legado", "title-queued-for-deletion": "Na fila para exclusão" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Base", "dashboard-preview": "Pré-visualização do painel", @@ -10788,6 +11370,11 @@ "label-current-step": "Etapa atual", "label-pending-step": "Etapa pendente" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Editar a configuração", "button-edit": "Editar", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Não é possível fazer extração de um repositório instável" }, "synchronize-step": { - "error-no-job-id": "Falha ao iniciar a tarefa", - "error-no-repository-name": "Nenhum nome de repositório foi informado", - "error-starting-job": "Erro ao iniciar a tarefa", "synchronization-description": "Incluir confirmações para cada valor histórico", "synchronization-options": "Opções de sincronização" }, @@ -10809,6 +11393,7 @@ "go-to": "Ir para", "make-sure": "Certifique-se de incluir essas permissões" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Os recursos não poderão ser criados, editados ou excluídos durante este processo. Na última etapa, eles desaparecerão.", "alert-point-2": "Assim que o provisionamento for concluído, os recursos reaparecerão e serão gerenciados por meio de armazenamento externo.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Aplicar a toda a linha", "label-background-display-mode": "Modo de exibição de plano de fundo", - "label-wrap-text": "Quebrar texto", "wrap-text": "Quebrar texto" }, "column-alignment-options": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 176e04062db..e3e9a7ab70f 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Editar grupo de avaliação" }, "group-loader": { - "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}" + "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "O grupo está a ser eliminado" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Configurar", - "draft-new-rule": "Elabore o rascunho de uma nova regra", "ds-error": { "title": "Não é possível carregar regras para esta origem de dados" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Nova regra de alerta", "new-datasource-recording-rule": "Nova regra de gravação da origem de dados", "new-grafana-recording-rule": "Nova regra de gravação da Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Mostrar mais... " }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Recarregar a pré-visualização", + "empty-state": "", "label-preview": "Pré-visualizar", "refresh": "Atualizar" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Nome do atributo do token de ID", "login-attribute-path-description": "Expressão JMESPath a utilizar para a pesquisa de início de sessão do utilizador a partir do token de ID do utilizador.", "login-attribute-path-label": "Caminho do atributo de início de sessão", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "A ID de cliente de identidade gerida da credencial de identidade federada da sua aplicação OAuth2.", "managed-identity-client-id-label": "ID de cliente de identidade gerida FIC", "name-attribute-path-description": "Expressão JMESPath a utilizar para a pesquisa de início de sessão do utilizador a partir do token de ID do utilizador. \nEste nome será utilizado como o nome a apresentar do utilizador.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Ativado", "text-badge-not-enabled": "Não ativado" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "O ponto final .well-known/openid-configuration para o seu IdP", "title-open-id-connect-discovery-url": "URL de descoberta do OpenID Connect" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Cancelar", "cannot-move-folders": "Não é possível mover as pastas", "confirmation-text": "Eliminar", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Esta pasta está vazia" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} regra de alerta", "alertRule_other": "{{count}} regras de alerta", @@ -3554,6 +3582,7 @@ "empty-message": "Nenhuma pasta encontrada", "error-title": "Erro ao carregar pastas", "non-folder-item": "Não pasta {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Pesquisar pastas", "unknown-error": "Erro desconhecido" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "Adicionar {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Alterar fundo do painel", "panel-description": "Alterar descrição do painel", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Painel de controlo", + "element": "", "local-variable": "Variável local", "multiple-elements": "Vários elementos", "multiple-elements-delete-text": "Tem a certeza de que pretende eliminar estes elementos?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Outra pessoa atualizou este painel de controlo", "would-still-dashboard": "Ainda pretende guardar este painel de controlo?" }, - "schema-v2-library-panels-export": "A funcionalidade do painel de controlo dinâmico é experimental e não tem paridade de funcionalidade total com o comportamento atual dos painéis de controlo. Baseia-se num novo formato de esquema, que não suporta painéis de biblioteca. Isto significa que, ao exportar o painel de controlo para o utilizar noutra instância, não incluiremos painéis de biblioteca. Tencionamos suportá-los à medida que avançamos no <2>ciclo de vida da funcionalidade.", - "schema-v2-library-panels-export-title": "O Esquema de Painel de Controlo V2 ainda não suporta a exportação de painéis de biblioteca para serem utilizados noutra instância", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Painel de controlo alterado de forma irreversível" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Hora de início:" } }, + "share-span": "", "warnings": "Avisos" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Mês anterior", "select-placeholder": "Selecione data/hora" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Fechar" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Nenhum valor", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} de {{numRows}} linhas" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} de {{numRows}} linhas", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Etiquetas" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Copiar linha de registo", "hide-details": "Mostrar detalhes do registo", "icon-label": "Menu de registo", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Afixar registo", "show-context": "Mostrar contexto", "show-details": "Ocultar detalhes do registo", @@ -9682,7 +10196,9 @@ "aria-label": "Ajuda" }, "invite-user": { - "invite-new-member-button": "Convidar um novo membro" + "invite-button": "Convidar", + "invite-new-member-button": "Convidar um novo membro", + "invite-tooltip": "" }, "item": { "add-bookmark": "Adicionar aos Marcadores", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Esta funcionalidade está atualmente em desenvolvimento ativo. Para a melhor experiência e as melhorias mais recentes, recomendamos a utilização da <2>compilação noturna da Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Adicione um nome claro para esta ligação de repositório", "empty": "Esvaziar", @@ -10460,8 +10998,12 @@ "label-display-name": "Nome do visor", "placeholder-my-repository-connection": "A minha ligação de repositório", "text-loading-resource-information": "A carregar informações de recursos...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} ficheiro", - "files-count_other": "{{count}} ficheiros" + "files-count_other": "{{count}} ficheiros", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Verificar" @@ -10472,36 +11014,20 @@ "button-save": "Guardar", "button-saving": "A guardar...", "description-enabled": "Assim que a extração automática estiver ativada, o alvo não pode ser alterado.", - "description-path": "Caminho para um subdiretório no repositório Git", "description-read-only": "Os recursos não podem ser modificados através da Grafana.", - "description-repository-url": "Introduza o URL do repositório GitHub", "description-title": "Um nome legível para a configuração", "error-required": "Este campo é obrigatório.", "error-save-repository": "Não foi possível guardar as definições do repositório", - "error-valid-github-url": "Introduza um URL de repositório GitHub válido", "label-automatic-pulling": "Extração automática", - "label-branch": "Ramo", "label-enabled": "Ativado", - "label-github-token": "Token do GitHub", "label-interval-seconds": "Intervalo (segundos)", - "label-local-path": "Caminho local", - "label-path": "Caminho", - "label-pr-workflow": "Ativar a opção de pedido de extração ao guardar", "label-repository-type": "Tipo de repositório", - "label-repository-url": "URL do repositório", "label-target": "Destino", "label-title": "Título", "option-entire-instance": "Instância inteira", - "option-github": "GitHub", - "option-local": "Local", "option-managed-folder": "Pasta gerida", - "placeholder-branch": "principal", - "placeholder-github-token": "ghp_oSeuTokenAqui1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/nomedeutilizador/nome-repositorio", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/caminho/para/repo", - "placeholder-my-config": "A minha configuração", - "placeholder-select-repository-type": "Selecionar tipo de repositório" + "placeholder-my-config": "A minha configuração" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Configure webhooks para obter atualizações instantâneas na Grafana assim que as alterações forem efetuadas. Reveja e aprove as alterações com pedidos de extração antes da sua publicação.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Configurar", - "configure-file": "Configurar o provisionamento de ficheiros", - "configure-git-sync": "Configurar sincronização do Git", "repository-limit-info-alert": "Limite do repositório atingido ({{count}})" }, - "connect-step": { - "description-branch": "Ramo a utilizar para o repositório GitHub", - "description-github-path": "Este é o caminho para um subdiretório no seu repositório GitHub onde os painéis de controlo serão armazenados e provisionados a partir de", - "description-paste-your-git-hub-personal-access-token": "Cole o seu token de acesso pessoal do GitHub", - "description-repository-url": "Colar o URL do seu repositório GitHub", - "error-field-required": "Este campo é obrigatório.", - "error-invalid-github-url": "Introduza um URL de repositório GitHub válido", - "label-access-token": "Token de acesso do GitHub", - "label-branch": "Nome do ramo", - "label-local-path": "Caminho local", - "label-path": "Caminho para o subdiretório no repositório", - "label-repository-url": "URL do repositório Github", - "placeholder-branch": "principal", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/caminho/para/repo" - }, "delete-repository-button": { "button-delete": "Eliminar", "confirm-delete-repository": "Tem a certeza de que pretende eliminar a configuração do repositório?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Atualizações instantâneas e pedidos de extração com webhooks.", "title-visual-previews-in-pull-requests": "Pré-visualizações em pedidos de extração com renderização de imagens" }, + "error-title-default": "", "expanded-row": { "job-specification": "Definição do trabalho", - "summary": "Resumo", - "title-error": "Erro" + "summary": "Resumo" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Configure as opções de funcionalidades necessárias" }, - "manage-dashboards-provision-updates-automatically": "Gerir painéis de controlo como código no GitHub e fornecer atualizações automaticamente", - "manage-your-dashboards-with-remote-provisioning": "Começar a utilizar o GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Armazenar painéis em armazenamento controlado por versão para melhor organização e acompanhamento do histórico" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Adicionar uma pré-visualização de imagem das alterações do painel de controlo nos pedidos de extração. As imagens dos seus painéis de controlo Grafana serão partilhadas no seu repositório Git e visíveis para qualquer pessoa com acesso ao repositório.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Requer renderização de imagens. <2>Configurar a renderização de imagens", - "description-often-shall-instance-updates-git-hub": "Com que frequência a instância deve obter atualizações do GitHub?", - "description-pr-enable-description": "Permite que os utilizadores escolham se desejam abrir um pedido pull ao guardar as alterações. Se o repositório não permitir alterações diretas ao ramo principal, ainda poderá ser necessário um pedido pull.", + "description-preview-requirements": "", "description-read-only": "Os recursos não podem ser modificados através da Grafana.", - "description-webhooks-enable": "Permite que os utilizadores escolham se desejam abrir um pedido pull ao guardar as alterações. Se o repositório não permitir alterações diretas ao ramo principal, ainda poderá ser necessário um pedido pull.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Ativar as pré-visualizações do painel de controlo em pedidos de extração", - "label-pr-workflow": "Ativar a opção de pedido de extração ao guardar", + "label-generate-dashboard-previews": "", "label-read-only": "Apenas para leitura", - "label-update-instance-interval-seconds": "Atualizar intervalo de instância (segundos)", - "placeholder": "60", - "text-setup-later": "Pode sempre configurar isto mais tarde", - "title-enhance-github": "Melhore a sua experiência GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Não foram encontrados resultados que correspondam à sua consulta", @@ -10640,6 +11145,57 @@ "header": "Provisionamento", "subtitle-provisioning-feature": "Ver e gerir as suas ligações de provisionamento" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Não encontrado" }, @@ -10666,9 +11222,17 @@ "starting": "A iniciar...", "status": { "title-error-running-job": "Erro ao executar tarefa", - "title-job-completed-successfully": "Trabalho concluído com sucesso" + "title-warning-running-job": "" }, - "summary": "Resumo" + "summary": "Resumo", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Definições", - "source-code": "Código da origem" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Armazenamento herdado", "title-queued-for-deletion": "Em fila para eliminação" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Base", "dashboard-preview": "Pré-visualização do painel de controlo", @@ -10788,6 +11370,11 @@ "label-current-step": "Passo atual", "label-pending-step": "Passo pendente" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Editar a configuração", "button-edit": "Editar", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Não foi possível obter um repositório que não está em bom estado" }, "synchronize-step": { - "error-no-job-id": "Falha ao iniciar a tarefa", - "error-no-repository-name": "Nenhum nome de repositório fornecido", - "error-starting-job": "Erro ao iniciar a tarefa", "synchronization-description": "Incluir commits para cada valor histórico", "synchronization-options": "Opções de sincronização" }, @@ -10809,6 +11393,7 @@ "go-to": "Aceda a", "make-sure": "Certifique-se de incluir estas permissões" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Os recursos não poderão ser criados, editados ou eliminados durante este processo. No último passo, irão desaparecer.", "alert-point-2": "Assim que o provisionamento estiver concluído, os recursos reaparecerão e serão geridos através de um armazenamento externo.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Aplicar a toda a linha", "label-background-display-mode": "Modo de exibição de fundo", - "label-wrap-text": "Ajustar texto", "wrap-text": "Ajustar texto" }, "column-alignment-options": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 6a522f41b98..5179df1fabd 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1563,7 +1564,8 @@ "title": "Редактирование группы оценки" }, "group-loader": { - "group-load-failed": "Не удалось загрузить правила из группы {{ groupName }} в {{ namespaceName }}" + "group-load-failed": "Не удалось загрузить правила из группы {{ groupName }} в {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Выполняется удаление группы" @@ -2486,7 +2488,6 @@ }, "rule-list": { "configure-datasource": "Настроить", - "draft-new-rule": "Составить новое правило", "ds-error": { "title": "Невозможно загрузить правила для этого источника данных" }, @@ -2512,6 +2513,7 @@ "new-alert-rule": "Новое правило оповещения", "new-datasource-recording-rule": "Новое правило записи источника данных", "new-grafana-recording-rule": "Новое правило записи Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Показать еще…" }, @@ -2828,6 +2830,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Обновить просмотр", + "empty-state": "", "label-preview": "Предварительный просмотр", "refresh": "Обновить" }, @@ -3282,6 +3285,11 @@ "id-token-attribute-name-label": "Путь атрибута идентификационного токена", "login-attribute-path-description": "Выражение JMESPath, используемое для поиска логина в идентификационном токене пользователя.", "login-attribute-path-label": "Путь атрибута логина", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "Идентификатор клиента управляемого удостоверения федеративных учетных данных вашего приложения OAuth2.", "managed-identity-client-id-label": "Идентификатор клиента управляемого удостоверения FIC", "name-attribute-path-description": "Выражение JMESPath, используемое для поиска имени пользователя в его идентификационном токене. \nЭто имя будет использоваться в качестве отображаемого имени пользователя.", @@ -3380,6 +3388,9 @@ "text-badge-enabled": "Включено", "text-badge-not-enabled": "Не включена" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Конечная точка .well-known/openid-configuration для вашего поставщика удостоверений (IdP)", "title-open-id-connect-discovery-url": "URL-адрес обнаружения OpenID Connect" @@ -3497,7 +3508,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Отмена", "cannot-move-folders": "Папки нельзя перемещать", "confirmation-text": "Удалить", @@ -3530,6 +3540,26 @@ "browse-view": { "this-folder-is-empty": "Папка пуста" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_few": "", + "failed-alert_many": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} правило оповещения", "alertRule_few": "{{count}} правила оповещения", @@ -3590,6 +3620,7 @@ "empty-message": "Папки не найдены", "error-title": "Ошибка загрузки папок", "non-folder-item": "{{itemKind}} {{itemUID}} не является папкой", + "root-title": "", "search-placeholder": "Поиск папок", "unknown-error": "Неизвестная ошибка" }, @@ -4573,6 +4604,10 @@ }, "edit-actions": { "add": "Добавить {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Изменить фон панели", "panel-description": "Изменить описание панели", @@ -4587,6 +4622,7 @@ "edit-pane": { "elements": { "dashboard": "Дашборд", + "element": "", "local-variable": "Локальная переменная", "multiple-elements": "Несколько элементов", "multiple-elements-delete-text": "Действительно удалить элементы?", @@ -5800,7 +5836,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5966,8 +6001,8 @@ "title-someone-else-has-updated-this-dashboard": "Дашборд обновлен другим пользователем", "would-still-dashboard": "Все равно сохранить дашборд?" }, - "schema-v2-library-panels-export": "Функциональность динамического дашборда является экспериментальной и не имеет полного паритета функциональных возможностей с текущим поведением дашбордов. Она основана на новом формате схемы, который не поддерживает панели библиотеки. Таким образом, экспорт дашборда для использования в другом экземпляре не будет включать панели библиотеки. Мы намерены поддерживать их по мере прохождения <2>жизненного цикла функции.", - "schema-v2-library-panels-export-title": "Дашборды схемы V2 пока не поддерживают экспорт панелей библиотеки для использования в другом экземпляре", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Дашборд необратимо изменен" }, "save-dashboard-form-common-options": { @@ -7173,6 +7208,7 @@ "start-time": "Время начала:" } }, + "share-span": "", "warnings": "Предупреждения" }, "span-filters": { @@ -7752,6 +7788,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7991,6 +8430,60 @@ "previous-label": "Предыдущий месяц", "select-placeholder": "Выбрать дату/время" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Закрыть" }, @@ -8038,6 +8531,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8142,7 +8649,10 @@ "no-data": "" }, "no-values-label": "Нет значений", - "pagination-summary": "Строки: {{itemsRangeStart}} - {{displayedEnd}} из {{numRows}}" + "pagination-summary": "Строки: {{itemsRangeStart}} - {{displayedEnd}} из {{numRows}}", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Теги" @@ -8891,8 +9401,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8907,6 +9420,9 @@ "copy-log": "Копировать строку журнала", "hide-details": "Показать сведения о журнале", "icon-label": "Меню журнала", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Закрепить журнал", "show-context": "Показать контекст", "show-details": "Скрыть сведения о журнале", @@ -9740,7 +10256,9 @@ "aria-label": "Справка" }, "invite-user": { - "invite-new-member-button": "Пригласить нового члена" + "invite-button": "Пригласить", + "invite-new-member-button": "Пригласить нового члена", + "invite-tooltip": "" }, "item": { "add-bookmark": "Добавить в закладки", @@ -10504,17 +11022,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "В настоящее время эта функция находится в стадии активной разработки. Чтобы обеспечить максимальное удобство пользования и получить доступ к последним улучшениям, рекомендуем использовать <2>ночную сборку Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Добавьте понятное имя для этого подключения к репозиторию.", "empty": "Пусто", @@ -10524,10 +11064,18 @@ "label-display-name": "Отображаемое имя", "placeholder-my-repository-connection": "Мое подключение к репозиторию", "text-loading-resource-information": "Загрузка информации о ресурсе...", + "dashboards-count_one": "", + "dashboards-count_few": "", + "dashboards-count_many": "", + "dashboards-count_other": "", "files-count_one": "{{count}} файл", "files-count_few": "{{count}} файла", "files-count_many": "{{count}} файлов", - "files-count_other": "{{count}} файла" + "files-count_other": "{{count}} файла", + "folders-count_one": "", + "folders-count_few": "", + "folders-count_many": "", + "folders-count_other": "" }, "check-repository": { "check": "Проверить" @@ -10538,36 +11086,20 @@ "button-save": "Сохранить", "button-saving": "Сохранение...", "description-enabled": "После включения автоматического внесения изменения целевой элемент невозможно изменить.", - "description-path": "Путь к подкаталогу в репозитории Git.", "description-read-only": "Ресурсы невозможно изменить через Grafana.", - "description-repository-url": "Введите URL-адрес репозитория GitHub.", "description-title": "Понятное название конфигурации", "error-required": "Поле является обязательным.", "error-save-repository": "Не удалось сохранить параметры репозитория", - "error-valid-github-url": "Введите действительный URL-адрес репозитория GitHub", "label-automatic-pulling": "Автоматическое включение изменений", - "label-branch": "Ветвь", "label-enabled": "Включено", - "label-github-token": "Токен GitHub", "label-interval-seconds": "Интервал (в секундах)", - "label-local-path": "Локальный путь", - "label-path": "Путь", - "label-pr-workflow": "Включить параметр запроса на включение изменений при сохранении", "label-repository-type": "Тип репозитория", - "label-repository-url": "URL-адрес репозитория", "label-target": "Цель", "label-title": "Название", "option-entire-instance": "Весь экземпляр", - "option-github": "GitHub", - "option-local": "Локальный", "option-managed-folder": "Управляемая папка", - "placeholder-branch": "основной", - "placeholder-github-token": "ghp_вашТокен1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/имя-пользователя/название-репозитория", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/путь/к/репозиторию", - "placeholder-my-config": "Моя конфигурация", - "placeholder-select-repository-type": "Выбрать тип репозитория" + "placeholder-my-config": "Моя конфигурация" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Настройте веб-перехватчики, чтобы получать мгновенные обновления в Grafana сразу после фиксации изменений. Просматривайте и утверждайте изменения с помощью соответствующих запросов перед их внедрением.", @@ -10581,27 +11113,8 @@ }, "connect-repository-button": { "configure": "Настроить", - "configure-file": "Настроить подготовку файлов", - "configure-git-sync": "Настроить Git Sync", "repository-limit-info-alert": "Достигнут лимит репозитория ({{count}})" }, - "connect-step": { - "description-branch": "Ветвь для использования в репозитории GitHub.", - "description-github-path": "Путь к подкаталогу в вашем репозитории GitHub, где будут храниться и подготавливаться дашборды.", - "description-paste-your-git-hub-personal-access-token": "Вставьте свой персональный токен доступа GitHub.", - "description-repository-url": "Вставьте URL-адрес своего репозитория GitHub.", - "error-field-required": "Поле является обязательным.", - "error-invalid-github-url": "Введите действительный URL-адрес репозитория GitHub", - "label-access-token": "Токен доступа GitHub", - "label-branch": "Название ветви", - "label-local-path": "Локальный путь", - "label-path": "Путь к подкаталогу в репозитории", - "label-repository-url": "URL-адрес репозитория GitHub", - "placeholder-branch": "основной", - "placeholder-github-token": "github_pat_вашТокен1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/путь/к/репозиторию" - }, "delete-repository-button": { "button-delete": "Удалить", "confirm-delete-repository": "Действительно удалить конфигурацию репозитория?", @@ -10633,17 +11146,17 @@ "title-instant-updates-requests-webhooks": "Мгновенные обновления и запросы на включение изменений с помощью веб-перехватчиков", "title-visual-previews-in-pull-requests": "Просмотр в запросах на включение изменений с визуализацией изображений" }, + "error-title-default": "", "expanded-row": { "job-specification": "Спецификация задания", - "summary": "Сводка", - "title-error": "Ошибка" + "summary": "Сводка" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Настроить необходимые переключатели функциональности" }, - "manage-dashboards-provision-updates-automatically": "Управляйте дашбордами как кодом в GitHub и автоматически выполняйте обновления", - "manage-your-dashboards-with-remote-provisioning": "Начало работы с GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Храните дашборды в хранилище с контролем версий для лучшей организации и отслеживания журналов" }, "file-history-page": { @@ -10664,18 +11177,16 @@ }, "finish-step": { "description-enable-previews": "Обеспечивает возможность просмотра изображений с изменениями дашборда в запросах на включение изменений. Изображения дашбордов Grafana будут публиковаться в вашем репозитории Git и видны всем, у кого есть доступ к репозиторию.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Требует визуализации изображений. <2>Настройте визуализацию изображений.", - "description-often-shall-instance-updates-git-hub": "Как часто экземпляр будет извлекать обновления из GitHub?", - "description-pr-enable-description": "Позволяет пользователям выбрать, следует ли открывать запрос на включение при сохранении изменений. Если репозиторий не позволяет вносить прямые изменения в основную ветвь, все равно может потребоваться запрос на включение изменений.", + "description-preview-requirements": "", "description-read-only": "Ресурсы невозможно изменить через Grafana.", - "description-webhooks-enable": "Позволяет пользователям выбрать, следует ли открывать запрос на включение при сохранении изменений. Если репозиторий не позволяет вносить прямые изменения в основную ветвь, все равно может потребоваться запрос на включение изменений.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Включить просмотр дашбордов в запросах на включение изменений", - "label-pr-workflow": "Включить параметр запроса на включение изменений при сохранении", + "label-generate-dashboard-previews": "", "label-read-only": "Только для чтения", - "label-update-instance-interval-seconds": "Интервал обновления экземпляра (в секундах)", - "placeholder": "60", - "text-setup-later": "Настройку всегда можно выполнить позже", - "title-enhance-github": "Расширение возможностей GitHub" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "По вашему запросу ничего не найдено", @@ -10706,6 +11217,57 @@ "header": "Подготовка к работе", "subtitle-provisioning-feature": "Просмотр подключений для подготовки и управлением ими" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Не найдено" }, @@ -10732,9 +11294,17 @@ "starting": "Запуск...", "status": { "title-error-running-job": "Ошибка при выполнении задания", - "title-job-completed-successfully": "Задание завершено" + "title-warning-running-job": "" }, - "summary": "Сводка" + "summary": "Сводка", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10760,7 +11330,7 @@ }, "repository-actions": { "settings": "Параметры", - "source-code": "Исходный код" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10825,6 +11395,24 @@ "title-legacy-storage": "Устаревшее хранилище", "title-queued-for-deletion": "В очереди на удаление" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Основа", "dashboard-preview": "Просмотр дашборда", @@ -10854,6 +11442,11 @@ "label-current-step": "Текущий шаг", "label-pending-step": "Предстоящий шаг" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Редактировать конфигурацию", "button-edit": "Редактировать", @@ -10864,9 +11457,6 @@ "tooltip-unhealthy-repository": "Невозможно внести изменения в неисправный репозиторий" }, "synchronize-step": { - "error-no-job-id": "Не удалось запустить задание", - "error-no-repository-name": "Не указано название репозитория", - "error-starting-job": "Ошибка при запуске задания", "synchronization-description": "Включить фиксации для каждого ранее зафиксированного значения", "synchronization-options": "Параметры синхронизации" }, @@ -10875,6 +11465,7 @@ "go-to": "Перейти к", "make-sure": "Обязательно включите эти разрешения" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Во время этого процесса невозможно создавать, редактировать или удалять ресурсы. На последнем шаге они исчезнут.", "alert-point-2": "После завершения подготовки ресурсы снова появятся и будут управляться через внешнее хранилище.", @@ -11954,7 +12545,6 @@ }, "label-apply-to-entire-row": "Применить ко всей строке", "label-background-display-mode": "Режим отображения фона", - "label-wrap-text": "Перенос текста", "wrap-text": "Перенос текста" }, "column-alignment-options": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 9a41fd4a1fa..68b8aa42096 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Redigera utvärderingsgrupp" }, "group-loader": { - "group-load-failed": "Det gick inte att ladda reglerna från gruppen {{ groupName }} i {{ namespaceName }}" + "group-load-failed": "Det gick inte att ladda reglerna från gruppen {{ groupName }} i {{ namespaceName }}", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Gruppen raderas" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Konfigurera", - "draft-new-rule": "Skapa utkast till en ny regel", "ds-error": { "title": "Kan inte ladda regler för denna datakälla" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Ny varningsregel", "new-datasource-recording-rule": "Ny registreringsregel för datakälla", "new-grafana-recording-rule": "Ny registreringsregel för Grafana", + "new-rule-for-export": "", "pagination": { "next-page": "Visa mer …" }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Uppdatera förhandsgranskning", + "empty-state": "", "label-preview": "Förhandsgranska", "refresh": "Uppdatera" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "ID-tokenattributnamn", "login-attribute-path-description": "JMESPath-uttryck som ska användas för användarens inloggningssökning från användar-ID-token.", "login-attribute-path-label": "Sökväg för inloggningsattribut", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "Klient-ID för den hanterade identiteten för din OAuth2-apps federerade identitetsuppgift.", "managed-identity-client-id-label": "FIC klient-ID för hanterad identitet", "name-attribute-path-description": "JMESPath-uttryck som ska användas för användarnamnsökning från användar-ID-token.Det här namnet kommer att användas som användarens visningsnamn.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Aktiverad", "text-badge-not-enabled": "Ej aktiverat" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Slutpunkten .well-known/openid-configuration för din identitetsleverantör", "title-open-id-connect-discovery-url": "Webbadress för OpenID Connect Discovery" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "Avbryt", "cannot-move-folders": "Mapparna kan inte flyttas", "confirmation-text": "Ta bort", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Denna mapp är tom" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} varningsregel", "alertRule_other": "{{count}} varningsregel", @@ -3554,6 +3582,7 @@ "empty-message": "Inga mappar hittades", "error-title": "Fel vid laddning av mappar", "non-folder-item": "Icke-mapp {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Sök mappar", "unknown-error": "Okänt fel" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "Lägg till {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Ändra panelbakgrund", "panel-description": "Ändra panelbeskrivning", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Instrumentpanel", + "element": "", "local-variable": "Lokal variabel", "multiple-elements": "Flera element", "multiple-elements-delete-text": "Är du säker på att du vill radera dessa element?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Någon annan har uppdaterat denna instrumentpanel", "would-still-dashboard": "Vill du fortfarande spara denna instrumentpanel?" }, - "schema-v2-library-panels-export": "Funktionen för dynamiska instrumentpaneler är experimentell och har ännu inte fullständig funktionsparitet med nuvarande instrumentpanelbeteende. Det är baserat på ett nytt schemaformat som saknar stöd för bibliotekspaneler. Det innebär att om du vill använda instrumentpanelen i en annan instans och exporterar den kommer inte bibliotekspaneler att inkluderas under exporten. Vi har för avsikt att inkludera stöd för dem när vi går vidare med funktionens <2>livscykel.", - "schema-v2-library-panels-export-title": "Instrumentpanelschema V2 saknar för närvarande stöd för export av bibliotekspaneler för användning i en annan instans", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Instrumentpanelen har ändrats oåterkalleligt" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Starttid:" } }, + "share-span": "", "warnings": "Varningar" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Föregående månad", "select-placeholder": "Välj datum/tid" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Stäng" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Inga värden", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} av {{numRows}} rader" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} av {{numRows}} rader", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Taggar" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Kopiera loggrad", "hide-details": "Visa loggdetaljer", "icon-label": "Loggmeny", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Fäst logg", "show-context": "Visa sammanhang", "show-details": "Dölj loggdetaljer", @@ -9682,7 +10196,9 @@ "aria-label": "Hjälp" }, "invite-user": { - "invite-new-member-button": "Bjud in ny medlem" + "invite-button": "Bjud in", + "invite-new-member-button": "Bjud in ny medlem", + "invite-tooltip": "" }, "item": { "add-bookmark": "Lägg till bokmärken", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Den här funktionen är för närvarande under aktiv utveckling. För den bästa upplevelsen och de senaste förbättringarna rekommenderar vi att du använder <2> nightly-versionen av Grafana." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Lägg till ett tydligt namn för den här lagringsplatsanslutningen", "empty": "Tom", @@ -10460,8 +10998,12 @@ "label-display-name": "Visningsnamn", "placeholder-my-repository-connection": "Min lagringsplatsanslutning", "text-loading-resource-information": "Läser in resursinformation …", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} filer", - "files-count_other": "{{count}} filer" + "files-count_other": "{{count}} filer", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Kolla" @@ -10472,36 +11014,20 @@ "button-save": "Spara", "button-saving": "Sparar ...", "description-enabled": "När automatisk hämtning är aktiverad kan målet inte ändras.", - "description-path": "Sökväg till en underkatalog på Git-lagringsplatsen", "description-read-only": "Resurser kan inte ändras via Grafana.", - "description-repository-url": "Ange webbadressen för GitHub-lagringsplatsen", "description-title": "Ett användarvänligt namn för konfigurationen", "error-required": "Detta fält är obligatoriskt.", "error-save-repository": "Det gick inte att spara lagringsplatsinställningar", - "error-valid-github-url": "Ange en giltig webbadress till GitHub-lagringsplatsen", "label-automatic-pulling": "Automatisk hämtning", - "label-branch": "Gren", "label-enabled": "Aktiverad", - "label-github-token": "GitHub-token", "label-interval-seconds": "Intervall (sekunder)", - "label-local-path": "Lokal sökväg", - "label-path": "Sökväg", - "label-pr-workflow": "Aktivera alternativ för hämtningsbegäran när du sparar", "label-repository-type": "Typ av lagringsplats", - "label-repository-url": "Webbadress för lagringsplats", "label-target": "Mål", "label-title": "Titel", "option-entire-instance": "Hela instansen", - "option-github": "GitHub", - "option-local": "Lokal", "option-managed-folder": "Hanterad mapp", - "placeholder-branch": "viktigast", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Min konfiguration", - "placeholder-select-repository-type": "Välj typ av lagringsplats" + "placeholder-my-config": "Min konfiguration" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>Konfigurera webhooks för att få omedelbara uppdateringar i Grafana så snart ändringar görs. Granska och godkänn ändringar med hjälp av pull-förfrågningar innan de publiceras.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Konfigurera", - "configure-file": "Konfigurera provisionering med filer", - "configure-git-sync": "Konfigurera Git Sync", "repository-limit-info-alert": "Gränsen för lagringsplatsen har uppnåtts ({{count}})" }, - "connect-step": { - "description-branch": "Gren som ska användas för GitHub-lagringsplatsen", - "description-github-path": "Det här är sökvägen till en underkatalog i din GitHub-lagringsplats där paneler kommer att lagras och provisioneras från", - "description-paste-your-git-hub-personal-access-token": "Klistra in din personliga GitHub-åtkomsttoken", - "description-repository-url": "Klistra in webbadressen för GitHub-lagringsplatsen", - "error-field-required": "Detta fält är obligatoriskt.", - "error-invalid-github-url": "Ange en giltig webbadress till GitHub-lagringsplatsen", - "label-access-token": "Åtkomsttoken för GitHub", - "label-branch": "Grennamn", - "label-local-path": "Lokal sökväg", - "label-path": "Sökväg till underkatalog på lagringsplatsen", - "label-repository-url": "Webbadress för GitHub-lagringsplatsen", - "placeholder-branch": "viktigast", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Ta bort", "confirm-delete-repository": "Är du säker på att du vill radera lagringsplatskonfigurationen?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Omedelbara uppdateringar och pull-förfrågningar med webhooks.", "title-visual-previews-in-pull-requests": "Visuella förhandsgranskningar i pull-förfrågningar med bildrendering" }, + "error-title-default": "", "expanded-row": { "job-specification": "Jobbeskrivning", - "summary": "Sammanfattning", - "title-error": "Fel" + "summary": "Sammanfattning" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Konfigurera obligatoriska funktionsväxlare" }, - "manage-dashboards-provision-updates-automatically": "Hantera instrumentpaneler som kod i GitHub och provisionering av uppdateringar automatiskt", - "manage-your-dashboards-with-remote-provisioning": "Kom igång med GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Lagra instrumentpaneler med versionsstyrd lagring för bättre organisering och historikspårning" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Lägger till en förhandsgranskning av ändringar i instrumentpanelen för pull-förfrågningar. Bilder av dina Grafana-instrumentpaneler kommer att delas på din Git-lagringsplats och vara synliga för alla med lagringsåtkomst.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Kräver bildrendering. <2>Konfigurera bildrendering", - "description-often-shall-instance-updates-git-hub": "Hur ofta ska instansen hämta uppdateringar från GitHub?", - "description-pr-enable-description": "Låter användare välja om en pull-begäran ska öppnas när ändringar sparas. Om lagringsplatsen inte tillåter direkta ändringar i huvudgrenen kan en pull-begäran fortfarande krävas.", + "description-preview-requirements": "", "description-read-only": "Resurser kan inte ändras via Grafana.", - "description-webhooks-enable": "Låter användare välja om en pull-begäran ska öppnas när ändringar sparas. Om lagringsplatsen inte tillåter direkta ändringar i huvudgrenen kan en pull-begäran fortfarande krävas.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Aktivera förhandsgranskningar av panelen i pull-förfrågningar", - "label-pr-workflow": "Aktivera alternativ för hämtningsbegäran när du sparar", + "label-generate-dashboard-previews": "", "label-read-only": "Skrivskyddad", - "label-update-instance-interval-seconds": "Uppdatera instansintervall (sekunder)", - "placeholder": "60", - "text-setup-later": "Du kan alltid konfigurera detta senare", - "title-enhance-github": "Förbättra din GitHub-upplevelse" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Inga resultat som matchar din fråga", @@ -10640,6 +11145,57 @@ "header": "Provisionering", "subtitle-provisioning-feature": "Visa och hantera dina provisioneringsanslutningar" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Hittades inte" }, @@ -10666,9 +11222,17 @@ "starting": "Startar …", "status": { "title-error-running-job": "Ett fel uppstod när ett jobb kördes", - "title-job-completed-successfully": "Jobbet har slutförts" + "title-warning-running-job": "" }, - "summary": "Sammanfattning" + "summary": "Sammanfattning", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Inställningar", - "source-code": "Källkod" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Äldre lagring", "title-queued-for-deletion": "Köad för radering" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Bas", "dashboard-preview": "Förhandsgranskning av instrumentpanel", @@ -10788,6 +11370,11 @@ "label-current-step": "Nuvarande steg", "label-pending-step": "Väntande steg" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Redigera konfigurationen", "button-edit": "Redigera", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "Det gick inte att hämta en ohälsosam lagringsplats" }, "synchronize-step": { - "error-no-job-id": "Misslyckades att starta", - "error-no-repository-name": "Inget lagringsplatsnamn angivet", - "error-starting-job": "Fel vid start av jobb", "synchronization-description": "Inkludera åtaganden för varje historiskt värde", "synchronization-options": "Synkroniseringsalternativ" }, @@ -10809,6 +11393,7 @@ "go-to": "Gå till", "make-sure": "Se till att inkludera dessa behörigheter" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Resurser kan inte skapas, redigeras eller raderas under den här processen. I det sista steget kommer de att försvinna.", "alert-point-2": "När provisionering är klar kommer resurserna att visas igen och kan hanteras via extern lagring.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Tillämpa på hela raden", "label-background-display-mode": "Bakgrundsvisningsläge", - "label-wrap-text": "Radbryt text", "wrap-text": "Radbryt text" }, "column-alignment-options": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 350ffea8fb3..85c673683f2 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1551,7 +1552,8 @@ "title": "Değerlendirme grubunu düzenle" }, "group-loader": { - "group-load-failed": "{{ namespaceName }} alanındaki {{ groupName }} grubundan kurallar yüklenemedi" + "group-load-failed": "{{ namespaceName }} alanındaki {{ groupName }} grubundan kurallar yüklenemedi", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "Grup siliniyor" @@ -2466,7 +2468,6 @@ }, "rule-list": { "configure-datasource": "Yapılandır", - "draft-new-rule": "Yeni bir kural taslağı oluştur", "ds-error": { "title": "Bu veri kaynağı için kurallar yüklenemiyor" }, @@ -2492,6 +2493,7 @@ "new-alert-rule": "Yeni uyarı kuralı", "new-datasource-recording-rule": "Yeni veri kaynağı kayıt kuralı", "new-grafana-recording-rule": "Yeni Grafana kayıt kuralı", + "new-rule-for-export": "", "pagination": { "next-page": "Daha fazla göster..." }, @@ -2802,6 +2804,7 @@ }, "template-preview": { "aria-label-refresh-preview": "Ön izlemeyi yenile", + "empty-state": "", "label-preview": "Ön izleme", "refresh": "Yenile" }, @@ -3256,6 +3259,11 @@ "id-token-attribute-name-label": "Kimlik belirteci özniteliği adı", "login-attribute-path-description": "Kullanıcı kimliği belirtecinden giriş bilgisini aramak için kullanılacak JMESPath ifadesi.", "login-attribute-path-label": "Giriş özniteliği yolu", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "OAuth2 uygulamanızın birleşik kimlik bilgilerinin yönetilen kimlik istemci kimliği.", "managed-identity-client-id-label": "FIC tarafından yönetilen kimlik istemci kimliği", "name-attribute-path-description": "Kullanıcı kimliği belirtecinden kullanıcı adını aramak için kullanılacak JMESPath ifadesi.\nBu ad, kullanıcının görünen adı olarak kullanılacaktır.", @@ -3354,6 +3362,9 @@ "text-badge-enabled": "Etkin", "text-badge-not-enabled": "Devre dışı" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "Kimlik sağlayıcınız (IdP) için .well-known/openid-configuration uç noktası", "title-open-id-connect-discovery-url": "OpenID Connect Discovery URL'sini girin" @@ -3471,7 +3482,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "İptal", "cannot-move-folders": "Klasörler taşınamaz", "confirmation-text": "Sil", @@ -3504,6 +3514,24 @@ "browse-view": { "this-folder-is-empty": "Klasör boş" }, + "bulk-action-resources-form": { + "failed-alert_one": "", + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_one": "{{count}} uyarı kuralı", "alertRule_other": "{{count}} uyarı kuralı", @@ -3554,6 +3582,7 @@ "empty-message": "Klasör bulunamadı", "error-title": "Klasörler yüklenirken hata oluştu", "non-folder-item": "Klasörsüz {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "Klasör ara", "unknown-error": "Bilinmeyen hata" }, @@ -4537,6 +4566,10 @@ }, "edit-actions": { "add": "{{typeName}} ekle", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "", "panel-background": "Panel arka planını değiştir", "panel-description": "Panel açıklamasını değiştir", @@ -4551,6 +4584,7 @@ "edit-pane": { "elements": { "dashboard": "Pano", + "element": "", "local-variable": "Yerel değişken", "multiple-elements": "Birden çok öge", "multiple-elements-delete-text": "Bu ögeleri silmek istediğinize emin misiniz?", @@ -5760,7 +5794,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5926,8 +5959,8 @@ "title-someone-else-has-updated-this-dashboard": "Başka biri bu panoyu güncelledi", "would-still-dashboard": "Yine de bu panoyu kaydetmek istiyor musunuz?" }, - "schema-v2-library-panels-export": "Dinamik pano işlevi henüz test aşamasındadır ve mevcut panoların tüm işlevlerini tam olarak karşılamamaktadır. Yeni şema biçimi henüz kütüphane panellerini desteklemiyor. Bu nedenle, dışa aktarılan panolarda kütüphane panelleri yer almayacaktır. Özellik <2>geliştirme süreci ilerledikçe bu desteğin eklenmesi planlanmaktadır.", - "schema-v2-library-panels-export-title": "Pano Şeması V2, kütüphane panellerinin başka bir örnekte kullanılmak üzere dışa aktarılmasını henüz desteklememektedir", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "Pano geri döndürülemez şekilde değiştirildi" }, "save-dashboard-form-common-options": { @@ -7131,6 +7164,7 @@ "start-time": "Başlangıç Tarihi:" } }, + "share-span": "", "warnings": "Uyarılar" }, "span-filters": { @@ -7710,6 +7744,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7949,6 +8386,60 @@ "previous-label": "Önceki ay", "select-placeholder": "Tarih ve saati seçin" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "Kapat" }, @@ -7996,6 +8487,20 @@ "field-name-picker": { "placeholder": "" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "", "name-fields-by-query": "", "name-fields-with-name": "", @@ -8100,7 +8605,10 @@ "no-data": "" }, "no-values-label": "Değer yok", - "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}}/{{numRows}} satır" + "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}}/{{numRows}} satır", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "Etiketler" @@ -8833,8 +9341,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "", + "open-assistant": "", "pin-line": "", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "" }, @@ -8849,6 +9360,9 @@ "copy-log": "Günlük satırını kopyala", "hide-details": "Günlük ayrıntılarını göster", "icon-label": "Günlük menüsü", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "Günlüğü sabitle", "show-context": "İçeriği göster", "show-details": "Günlük ayrıntılarını gizle", @@ -9682,7 +10196,9 @@ "aria-label": "Yardım" }, "invite-user": { - "invite-new-member-button": "Yeni üye davet edin" + "invite-button": "Davet et", + "invite-new-member-button": "Yeni üye davet edin", + "invite-tooltip": "" }, "item": { "add-bookmark": "Yer imlerine ekle", @@ -10440,17 +10956,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "Bu özellik şu anda aktif geliştirme aşamasındadır. En iyi deneyim ve en yeni geliştirmeler için Grafana'nın <2>gecelik derleme sürümünü kullanmanızı öneririz." }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "Bu depo bağlantısı için net bir ad ekleyin", "empty": "Boş", @@ -10460,8 +10998,12 @@ "label-display-name": "Görünen ad", "placeholder-my-repository-connection": "Depo bağlantım", "text-loading-resource-information": "Kaynak bilgileri yükleniyor...", + "dashboards-count_one": "", + "dashboards-count_other": "", "files-count_one": "{{count}} dosya", - "files-count_other": "{{count}} dosya" + "files-count_other": "{{count}} dosya", + "folders-count_one": "", + "folders-count_other": "" }, "check-repository": { "check": "Kontrol et" @@ -10472,36 +11014,20 @@ "button-save": "Kaydet", "button-saving": "Kaydediliyor…", "description-enabled": "Otomatik çekme etkinleştirildikten sonra hedef değiştirilemez.", - "description-path": "Git deposundaki bir alt dizinin yolu", "description-read-only": "Kaynaklar Grafana aracılığıyla değiştirilemez.", - "description-repository-url": "GitHub depo URL'sini girin", "description-title": "Yapılandırma için okunabilir ad", "error-required": "Bu alan gereklidir.", "error-save-repository": "Depo ayarları kaydedilemedi", - "error-valid-github-url": "Lütfen geçerli bir GitHub depo URL'si girin", "label-automatic-pulling": "Otomatik çekme", - "label-branch": "Dal", "label-enabled": "Etkin", - "label-github-token": "GitHub belirteci", "label-interval-seconds": "Aralık (saniye)", - "label-local-path": "Yerel yol", - "label-path": "Yol", - "label-pr-workflow": "Kaydederken çekme isteği seçeneğini etkinleştir", "label-repository-type": "Depo türü", - "label-repository-url": "Depo URL'si", "label-target": "Hedef", "label-title": "Başlık", "option-entire-instance": "Tüm örnek", - "option-github": "GitHub", - "option-local": "Yerel", "option-managed-folder": "Yönetilen klasör", - "placeholder-branch": "ana", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "Yapılandırmam", - "placeholder-select-repository-type": "Depo türü seçin" + "placeholder-my-config": "Yapılandırmam" }, "config-form-github-collapse": { "description-realtime-feedback": "Değişiklikler gönderildiği anda Grafana'da anlık güncellemeler almak için <0>web kancalarını yapılandırın. Değişiklikleri kullanıma sunmadan önce çekme isteklerini kullanarak gözden geçirip onaylayın.", @@ -10515,27 +11041,8 @@ }, "connect-repository-button": { "configure": "Yapılandır", - "configure-file": "Dosya sağlamayı yapılandır", - "configure-git-sync": "Git Sync'i Yapılandır", "repository-limit-info-alert": "Depo sınırına ulaşıldı ({{count}})" }, - "connect-step": { - "description-branch": "GitHub deposunda kullanılacak dal", - "description-github-path": "Panoların saklanacağı ve sağlanacağı GitHub deponuzdaki alt dizinin yolu", - "description-paste-your-git-hub-personal-access-token": "GitHub kişisel erişim belirtecinizi yapıştırın", - "description-repository-url": "GitHub deponuzun URL'sini yapıştırın", - "error-field-required": "Bu alan gereklidir.", - "error-invalid-github-url": "Lütfen geçerli bir GitHub depo URL'si girin", - "label-access-token": "GitHub erişim belirteci", - "label-branch": "Dal adı", - "label-local-path": "Yerel yol", - "label-path": "Depodaki alt dizin yolu", - "label-repository-url": "GitHub depo URL'si", - "placeholder-branch": "ana", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "Sil", "confirm-delete-repository": "Depo yapılandırmasını silmek istediğinize emin misiniz?", @@ -10567,17 +11074,17 @@ "title-instant-updates-requests-webhooks": "Web kancalarıyla anlık güncellemeler ve çekme istekleri.", "title-visual-previews-in-pull-requests": "Çekme isteklerinde resimlerin işlenmesiyle birlikte görsel ön izleme" }, + "error-title-default": "", "expanded-row": { "job-specification": "İşin Özellikleri", - "summary": "Özet", - "title-error": "Hata" + "summary": "Özet" }, "features-list": { "actions": { "set-up-required-feature-toggles": "Gerekli özellik anahtarlarını ayarla" }, - "manage-dashboards-provision-updates-automatically": "Panoları GitHub'da kod olarak yönetin ve güncellemeleri otomatik sağlayın", - "manage-your-dashboards-with-remote-provisioning": "GitSync'i kullanmaya başlayın", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "Daha iyi organizasyon ve geçmiş takibi için panoları sürüm kontrollü bir depoda saklayın" }, "file-history-page": { @@ -10598,18 +11105,16 @@ }, "finish-step": { "description-enable-previews": "Çekme isteklerinde pano değişikliklerinin görsel ön izlemesini ekler. Grafana panolarınızın görselleri Git deponuzda paylaşılacak ve depo erişimi olan herkes tarafından görülebilecektir.", + "description-generate-dashboard-previews": "", "description-image-rendering": "Görsel oluşturma gerektirir. <2>Görsel oluşturmayı ayarlayın", - "description-often-shall-instance-updates-git-hub": "Örnek GitHub'dan ne sıklıkla güncelleme çekecek?", - "description-pr-enable-description": "Kullanıcıların değişiklikleri kaydederken bir çekme isteği açıp açmayacaklarını seçmelerine izin verir. Depo ana dala doğrudan değişikliklere izin vermiyorsa bir çekme isteği gerekebilir.", + "description-preview-requirements": "", "description-read-only": "Kaynaklar Grafana aracılığıyla değiştirilemez.", - "description-webhooks-enable": "Kullanıcıların değişiklikleri kaydederken bir çekme isteği açıp açmayacaklarını seçmelerine izin verir. Depo ana dala doğrudan değişikliklere izin vermiyorsa bir çekme isteği gerekebilir.", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "Çekme isteklerinde pano ön izlemelerini etkinleştir", - "label-pr-workflow": "Kaydederken çekme isteği seçeneğini etkinleştir", + "label-generate-dashboard-previews": "", "label-read-only": "Salt okunur", - "label-update-instance-interval-seconds": "Örnek güncelleme aralığı (saniye)", - "placeholder": "60", - "text-setup-later": "Bunu daha sonra istediğiniz zaman ayarlayabilirsiniz", - "title-enhance-github": "GitHub deneyiminizi geliştirin" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "Sorgunuzla eşleşen sonuç yok", @@ -10640,6 +11145,57 @@ "header": "Sağlama", "subtitle-provisioning-feature": "Sağlama bağlantılarınızı görüntüleyin ve yönetin" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "Bulunamadı" }, @@ -10666,9 +11222,17 @@ "starting": "Başlatılıyor...", "status": { "title-error-running-job": "İş çalıştırılırken hata oluştu", - "title-job-completed-successfully": "İş başarıyla tamamlandı" + "title-warning-running-job": "" }, - "summary": "Özet" + "summary": "Özet", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10694,7 +11258,7 @@ }, "repository-actions": { "settings": "Ayarlar", - "source-code": "Kaynak Kodu" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10759,6 +11323,24 @@ "title-legacy-storage": "Eski Depolama", "title-queued-for-deletion": "Silinmek üzere kuyruğa alındı" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "Temel", "dashboard-preview": "Pano Ön İzlemesi", @@ -10788,6 +11370,11 @@ "label-current-step": "Mevcut adım", "label-pending-step": "Bekleyen adım" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "Yapılandırmayı düzenle", "button-edit": "Düzenle", @@ -10798,9 +11385,6 @@ "tooltip-unhealthy-repository": "İyi durumda olmayan bir depo çekilemedi" }, "synchronize-step": { - "error-no-job-id": "İş başlatılamadı", - "error-no-repository-name": "Depo adı sağlanmadı", - "error-starting-job": "İş başlatılırken hata oluştu", "synchronization-description": "Her geçmiş değer için yürütmeyi dahil et", "synchronization-options": "Senkronizasyon seçenekleri" }, @@ -10809,6 +11393,7 @@ "go-to": "Şuraya git:", "make-sure": "Bu izinleri eklediğinizden emin olun" }, + "warning-title-default": "", "wizard": { "alert-point-1": "Bu işlem sırasında kaynaklar oluşturulamayacak, düzenlenemeyecek veya silinemeyecek. Son adımda kaynaklar kaybolacaktır.", "alert-point-2": "Sağlama işlemi tamamlandığında kaynaklar tekrar görünecek ve harici depolama üzerinden yönetilecektir.", @@ -11876,7 +12461,6 @@ }, "label-apply-to-entire-row": "Tüm satıra uygula", "label-background-display-mode": "Arka plan görüntüleme modu", - "label-wrap-text": "Metni satıra sığdır", "wrap-text": "Metni satıra sığdır" }, "column-alignment-options": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 1514f2b7632..0c7f83995a0 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1545,7 +1546,8 @@ "title": "编辑评估组" }, "group-loader": { - "group-load-failed": "从 {{ namespaceName }} 的组 {{ groupName }} 中加载规则失败" + "group-load-failed": "从 {{ namespaceName }} 的组 {{ groupName }} 中加载规则失败", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "正在删除组" @@ -2456,7 +2458,6 @@ }, "rule-list": { "configure-datasource": "配置", - "draft-new-rule": "起草新规则", "ds-error": { "title": "无法加载此数据源的规则" }, @@ -2482,6 +2483,7 @@ "new-alert-rule": "新建警报规则", "new-datasource-recording-rule": "新的数据源录制规则", "new-grafana-recording-rule": "新的 Grafana 录制规则", + "new-rule-for-export": "", "pagination": { "next-page": "显示更多..." }, @@ -2789,6 +2791,7 @@ }, "template-preview": { "aria-label-refresh-preview": "刷新预览", + "empty-state": "", "label-preview": "预览", "refresh": "刷新" }, @@ -3243,6 +3246,11 @@ "id-token-attribute-name-label": "ID 令牌属性名称", "login-attribute-path-description": "用于从用户 ID 令牌中查找用户登录的 JMESPath 表达式。", "login-attribute-path-label": "登录属性路径", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "OAuth2 应用的联合身份凭证的托管身份客户端 ID。", "managed-identity-client-id-label": "FIC 托管身份客户端 ID", "name-attribute-path-description": "用于从用户 ID 令牌中查找用户名的 JMESPath 表达式。此名称将用作用户的显示名称。", @@ -3341,6 +3349,9 @@ "text-badge-enabled": "已启用", "text-badge-not-enabled": "未启用" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "IdP 的 .well-known/openid-configuration 端点", "title-open-id-connect-discovery-url": "OpenID Connect 发现 URL" @@ -3458,7 +3469,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "取消", "cannot-move-folders": "文件夹无法移动", "confirmation-text": "删除", @@ -3491,6 +3501,23 @@ "browse-view": { "this-folder-is-empty": "此文件夹为空" }, + "bulk-action-resources-form": { + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_other": "{{count}} 条警告规则", "dashboard_other": "{{count}} 个仪表板", @@ -3536,6 +3563,7 @@ "empty-message": "未找到文件夹", "error-title": "加载文件夹时出错", "non-folder-item": "非文件夹 {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "搜索文件夹", "unknown-error": "未知错误" }, @@ -4519,6 +4547,10 @@ }, "edit-actions": { "add": "添加{{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "移动{{typeName}}", "panel-background": "更改面板背景", "panel-description": "更改面板描述", @@ -4533,6 +4565,7 @@ "edit-pane": { "elements": { "dashboard": "仪表板", + "element": "", "local-variable": "局部变量", "multiple-elements": "多个元素", "multiple-elements-delete-text": "您确定要删除这些元素吗?", @@ -5740,7 +5773,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5906,8 +5938,8 @@ "title-someone-else-has-updated-this-dashboard": "其他人已更新此数据面板", "would-still-dashboard": "您仍然要保存此数据面板吗?" }, - "schema-v2-library-panels-export": "动态数据面板功能是实验性的,并且与当前数据面板行为没有完全的功能对称性。它基于新的模式格式,且不支持库面板。这意味着,在导出数据面板以便在其他实例中使用时,其中不会包含库面板。我们打算在<2>生命周期功能中逐步支持它们。", - "schema-v2-library-panels-export-title": "数据面板模式 V2 暂不支持导出库面板以便在其他实例中使用", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "数据面板已不可逆地更改" }, "save-dashboard-form-common-options": { @@ -7110,6 +7142,7 @@ "start-time": "开始时间:" } }, + "share-span": "", "warnings": "警告" }, "span-filters": { @@ -7689,6 +7722,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7928,6 +8364,60 @@ "previous-label": "上个月", "select-placeholder": "选择日期/时间" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "关闭" }, @@ -7975,6 +8465,20 @@ "field-name-picker": { "placeholder": "选择字段" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "名称匹配正则表达式的字段", "name-fields-by-query": "查询返回的字段", "name-fields-with-name": "带名称的字段", @@ -8079,7 +8583,10 @@ "no-data": "" }, "no-values-label": "没有值", - "pagination-summary": "第 {{itemsRangeStart}} - {{displayedEnd}} 行,共 {{numRows}} 行" + "pagination-summary": "第 {{itemsRangeStart}} - {{displayedEnd}} 行,共 {{numRows}} 行", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "标签" @@ -8804,8 +9311,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "没有要显示的字段。", + "open-assistant": "", "pin-line": "固定日志", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "没有要显示的结果。" }, @@ -8820,6 +9330,9 @@ "copy-log": "复制日志行", "hide-details": "显示日志详情", "icon-label": "日志菜单", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "固定日志", "show-context": "显示上下文", "show-details": "隐藏日志详情", @@ -9653,7 +10166,9 @@ "aria-label": "帮助" }, "invite-user": { - "invite-new-member-button": "邀请新成员" + "invite-button": "", + "invite-new-member-button": "邀请新成员", + "invite-tooltip": "" }, "item": { "add-bookmark": "添加到书签", @@ -10408,17 +10923,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "此功能目前正在积极开发中。为了获得最佳体验和最新改进,我们建议您使用 Grafana 的<2>夜间版本。" }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "为此存储库连接添加明确的名称", "empty": "空", @@ -10428,7 +10965,9 @@ "label-display-name": "显示名称", "placeholder-my-repository-connection": "我的存储库连接", "text-loading-resource-information": "正在加载资源信息...", - "files-count_other": "{{count}} 个文件" + "dashboards-count_other": "", + "files-count_other": "{{count}} 个文件", + "folders-count_other": "" }, "check-repository": { "check": "检查" @@ -10439,36 +10978,20 @@ "button-save": "保存", "button-saving": "正在保存...", "description-enabled": "启用自动拉取后,将无法更改目标。", - "description-path": "Git 存储库中子目录的路径", "description-read-only": "无法通过 Grafana 修改资源。", - "description-repository-url": "输入 GitHub 存储库 URL", "description-title": "人类可读的配置名称", "error-required": "此字段是必填字段。", "error-save-repository": "保存存储库设置失败", - "error-valid-github-url": "请输入有效的 GitHub 存储库 URL", "label-automatic-pulling": "自动拉取", - "label-branch": "分支", "label-enabled": "已启用", - "label-github-token": "GitHub 令牌", "label-interval-seconds": "间隔(秒)", - "label-local-path": "本地路径", - "label-path": "路径", - "label-pr-workflow": "保存时启用拉取请求选项", "label-repository-type": "存储库类型", - "label-repository-url": "存储库 URL", "label-target": "目标", "label-title": "标题", "option-entire-instance": "整个实例", - "option-github": "GitHub", - "option-local": "本地", "option-managed-folder": "受管理的文件夹", - "placeholder-branch": "主要", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "我的配置", - "placeholder-select-repository-type": "选择存储库类型" + "placeholder-my-config": "我的配置" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>配置 Webhook,以便在提交更改后立即在 Grafana 中获取更新。使用拉取请求审核和批准更改,然后再发布。", @@ -10482,27 +11005,8 @@ }, "connect-repository-button": { "configure": "配置", - "configure-file": "配置文件预配", - "configure-git-sync": "配置 Git 同步", "repository-limit-info-alert": "已达到存储库限制 ({{count}})" }, - "connect-step": { - "description-branch": "用于 GitHub 存储库的分支", - "description-github-path": "这是 GitHub 存储库中子目录的路径,仪表板将在其中存储和预配", - "description-paste-your-git-hub-personal-access-token": "粘贴您的 GitHub 个人访问令牌", - "description-repository-url": "粘贴您 GitHub 存储库的 URL", - "error-field-required": "此字段是必填字段。", - "error-invalid-github-url": "请输入有效的 GitHub 存储库 URL", - "label-access-token": "GitHub 访问令牌", - "label-branch": "分支名称", - "label-local-path": "本地路径", - "label-path": "存储库中子目录的路径", - "label-repository-url": "Github 存储库 URL", - "placeholder-branch": "主要", - "placeholder-github-token": "github_pat_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "删除", "confirm-delete-repository": "您确定要删除存储库配置吗?", @@ -10534,17 +11038,17 @@ "title-instant-updates-requests-webhooks": "通过 Webhook 实现即时更新和拉取请求。", "title-visual-previews-in-pull-requests": "具有图像渲染功能的拉取请求可视化预览" }, + "error-title-default": "", "expanded-row": { "job-specification": "作业规范", - "summary": "摘要", - "title-error": "错误" + "summary": "摘要" }, "features-list": { "actions": { "set-up-required-feature-toggles": "设置所需的功能切换" }, - "manage-dashboards-provision-updates-automatically": "在 GitHub 中将数据面板作为代码进行管理,并自动预配更新", - "manage-your-dashboards-with-remote-provisioning": "开始使用 GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "将数据面板存储在版本控制的存储中,以便更好地整理内容和跟踪历史记录" }, "file-history-page": { @@ -10565,18 +11069,16 @@ }, "finish-step": { "description-enable-previews": "在拉取请求中添加数据面板更改的图像预览。Grafana 数据面板的图像将在 Git 存储库中共享,并对具有存储库访问权限的任何人可见。", + "description-generate-dashboard-previews": "", "description-image-rendering": "需要图像渲染。<2>设置图像渲染", - "description-often-shall-instance-updates-git-hub": "实例应多久从 GitHub 拉取一次更新?", - "description-pr-enable-description": "允许用户选择在保存更改时是否打开拉取请求。如果存储库不允许直接更改主分支,则仍可能需要拉取请求。", + "description-preview-requirements": "", "description-read-only": "无法通过 Grafana 修改资源。", - "description-webhooks-enable": "允许用户选择在保存更改时是否打开拉取请求。如果存储库不允许直接更改主分支,则仍可能需要拉取请求。", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "在拉取请求中启用数据面板预览", - "label-pr-workflow": "保存时启用拉取请求选项", + "label-generate-dashboard-previews": "", "label-read-only": "只读", - "label-update-instance-interval-seconds": "更新实例间隔(秒)", - "placeholder": "60", - "text-setup-later": "您可以稍后再进行设置", - "title-enhance-github": "增强您的 GitHub 体验" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "没有找到与您的查询匹配的结果", @@ -10607,6 +11109,57 @@ "header": "配置", "subtitle-provisioning-feature": "查看和管理您的预配连接" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "未找到" }, @@ -10633,9 +11186,17 @@ "starting": "正在启动...", "status": { "title-error-running-job": "运行作业时出错", - "title-job-completed-successfully": "作业已成功完成" + "title-warning-running-job": "" }, - "summary": "摘要" + "summary": "摘要", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10661,7 +11222,7 @@ }, "repository-actions": { "settings": "设置", - "source-code": "源代码" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10726,6 +11287,24 @@ "title-legacy-storage": "传统存储", "title-queued-for-deletion": "已进入队列等待删除" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "基本", "dashboard-preview": "数据面板预览", @@ -10755,6 +11334,11 @@ "label-current-step": "当前步骤", "label-pending-step": "待处理步骤" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "编辑配置", "button-edit": "编辑", @@ -10765,9 +11349,6 @@ "tooltip-unhealthy-repository": "无法拉取状态不良的存储库" }, "synchronize-step": { - "error-no-job-id": "启动作业失败", - "error-no-repository-name": "未提供存储库名称", - "error-starting-job": "启动作业时出错", "synchronization-description": "包括每个历史值的提交", "synchronization-options": "同步选项" }, @@ -10776,6 +11357,7 @@ "go-to": "前往", "make-sure": "务必包含以下权限" }, + "warning-title-default": "", "wizard": { "alert-point-1": "在此过程中将无法创建、编辑或删除资源。在最后一步中,它们将消失。", "alert-point-2": "预配完成后,资源将重新出现并通过外部存储进行管理。", @@ -11837,7 +12419,6 @@ }, "label-apply-to-entire-row": "应用到整行", "label-background-display-mode": "背景显示模式", - "label-wrap-text": "文本换行", "wrap-text": "文本换行" }, "column-alignment-options": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index e815a18a467..d701e5cb5e0 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -383,6 +383,7 @@ "ai": { "error-boundary": { "alert-rule-button": "", + "feedback-button": "", "improve-annotations-button": "", "improve-labels-button": "", "template-button": "", @@ -1545,7 +1546,8 @@ "title": "編輯評估群組" }, "group-loader": { - "group-load-failed": "無法從「{{ namespaceName }}」中的群組「{{ groupName }}」載入規則" + "group-load-failed": "無法從「{{ namespaceName }}」中的群組「{{ groupName }}」載入規則", + "no-rules": "" }, "group-status": { "content-the-group-is-being-deleted": "正在刪除群組" @@ -2456,7 +2458,6 @@ }, "rule-list": { "configure-datasource": "設定", - "draft-new-rule": "撰寫新規則", "ds-error": { "title": "無法載入此資料來源的規則" }, @@ -2482,6 +2483,7 @@ "new-alert-rule": "新的警報規則", "new-datasource-recording-rule": "新的資料來源錄製規則", "new-grafana-recording-rule": "新的 Grafana 錄製規則", + "new-rule-for-export": "", "pagination": { "next-page": "顯示更多內容…" }, @@ -2789,6 +2791,7 @@ }, "template-preview": { "aria-label-refresh-preview": "重新整理預覽", + "empty-state": "", "label-preview": "預覽", "refresh": "更新" }, @@ -3243,6 +3246,11 @@ "id-token-attribute-name-label": "ID 權杖屬性名稱", "login-attribute-path-description": "用於從使用者 ID 權杖中查找使用者登入的 JMESPath 表達式。", "login-attribute-path-label": "登入屬性路徑", + "login-prompt-consent": "", + "login-prompt-description": "", + "login-prompt-label": "", + "login-prompt-login": "", + "login-prompt-select-account": "", "managed-identity-client-id-description": "OAuth2 應用程式中聯合身分憑證的受控身分用戶端 ID。", "managed-identity-client-id-label": "FIC 受控身分用戶端 ID", "name-attribute-path-description": "用於從使用者 ID 權杖中查找使用者名稱的 JMESPath 表達式。\n此名稱將用作使用者的顯示名稱。", @@ -3341,6 +3349,9 @@ "text-badge-enabled": "已啟用", "text-badge-not-enabled": "未啟用" }, + "scim-banner": { + "message": "" + }, "server-discovery-modal": { "label-the-wellknownopenidconfiguration-endpoint-for-your-id-p": "您的 IdP 的 .well-known/openid-configuration 端點", "title-open-id-connect-discovery-url": "OpenID Connect 探索網址" @@ -3458,7 +3469,6 @@ "browse-dashboards": { "action": { "bulk-delete-provisioned-resources": "", - "bulk-delete-provisioned-resources-not-implemented": "", "cancel-button": "取消", "cannot-move-folders": "資料夾無法移動", "confirmation-text": "刪除", @@ -3491,6 +3501,23 @@ "browse-view": { "this-folder-is-empty": "此資料夾為空" }, + "bulk-action-resources-form": { + "failed-alert_other": "" + }, + "bulk-delete-resources-form": { + "button-cancel": "", + "button-delete": "", + "button-deleting": "", + "button-done": "", + "delete-warning": "", + "error-path-not-found": "", + "progress-title": "", + "success-message": "" + }, + "bulk-move-resources-form": { + "deleting": "", + "progress": "" + }, "counts": { "alertRule_other": "{{count}} 個警報規則", "dashboard_other": "{{count}} 個儀表板", @@ -3536,6 +3563,7 @@ "empty-message": "未發現資料夾", "error-title": "載入資料夾時發生錯誤", "non-folder-item": "非資料夾 {{itemKind}} {{itemUID}}", + "root-title": "", "search-placeholder": "搜尋資料夾", "unknown-error": "未知錯誤" }, @@ -4519,6 +4547,10 @@ }, "edit-actions": { "add": "新增 {{typeName}}", + "add-conditional-rule": "", + "edit-query-result-rule": "", + "edit-template-variable-rule": "", + "edit-time-range-rule": "", "move": "移動 {{typeName}}", "panel-background": "變更面板背景", "panel-description": "變更面板描述", @@ -4533,6 +4565,7 @@ "edit-pane": { "elements": { "dashboard": "儀表板", + "element": "", "local-variable": "本機變數", "multiple-elements": "多個元素", "multiple-elements-delete-text": "確定要刪除這些元素嗎?", @@ -5740,7 +5773,6 @@ "move-action": "", "move-read-only-message": "", "moving": "", - "partial-failure-warning": "", "target-path-label": "", "title-this-repository-is-read-only": "" }, @@ -5906,8 +5938,8 @@ "title-someone-else-has-updated-this-dashboard": "其他人已更新此儀表板", "would-still-dashboard": "仍要儲存此儀表板嗎?" }, - "schema-v2-library-panels-export": "動態儀表板功能僅供試驗,並未與目前的儀表板行為具有完全的功能平等性。它以新的模式格式為基礎,不支援資料庫面板。這表示在匯出儀表板以便在另一個執行個體中使用時,我們不會包含資料庫面板。隨著我們在功能<2>生命週期中取得進展,我們計劃為其提供支援。", - "schema-v2-library-panels-export-title": "儀表板架構 V2 尚不支援為了在另一個執行個體中使用而匯出資料庫面板", + "schema-v2-library-panels-export": "", + "schema-v2-library-panels-export-title": "", "title-dashboard-drastically-changed": "已永久變更儀表板" }, "save-dashboard-form-common-options": { @@ -7110,6 +7142,7 @@ "start-time": "開始時間:" } }, + "share-span": "", "warnings": "警告" }, "span-filters": { @@ -7689,6 +7722,409 @@ } } }, + "grafana-data": { + "valueFormats": { + "categories": { + "acceleration": { + "formats": { + "name-feet-sec": "", + "name-g-unit": "", + "name-meters-sec": "" + }, + "name": "" + }, + "angle": { + "formats": { + "name-arc-minutes": "", + "name-arc-seconds": "", + "name-degrees": "", + "name-gradian": "", + "name-radians": "" + }, + "name": "" + }, + "area": { + "formats": { + "name-acres": "", + "name-hectares": "", + "name-square-feet": "", + "name-square-meters": "", + "name-square-miles": "" + }, + "name": "" + }, + "boolean": { + "formats": { + "name-on-off": "", + "name-true-false": "", + "name-yes-no": "" + }, + "name": "" + }, + "computation": { + "formats": { + "name-eflops": "", + "name-flops": "", + "name-gflops": "", + "name-mflops": "", + "name-pflops": "", + "name-tflops": "", + "name-yflops": "", + "name-zflops": "" + }, + "name": "" + }, + "concentration": { + "formats": { + "name-g-m3": "", + "name-g-nm3": "", + "name-mg-dl": "", + "name-mg-m3": "", + "name-mg-nm3": "", + "name-mmol-l": "", + "name-ng-m3": "", + "name-ng-nm3": "", + "name-ppb": "", + "name-ppm": "", + "name-ug-m3": "", + "name-ug-nm3": "" + }, + "name": "" + }, + "currency": { + "formats": { + "name-bgn": "", + "name-brl": "", + "name-btc": "", + "name-chf": "", + "name-czk": "", + "name-dkk": "", + "name-eur": "", + "name-gbp": "", + "name-idr": "", + "name-ils": "", + "name-inr": "", + "name-isk": "", + "name-jpy": "", + "name-krw": "", + "name-mbtc": "", + "name-myr": "", + "name-nok": "", + "name-php": "", + "name-pln": "", + "name-pyg": "", + "name-rub": "", + "name-sek": "", + "name-try": "", + "name-uah": "", + "name-ubtc": "", + "name-usd": "", + "name-uyu": "", + "name-vnd": "", + "name-xpf": "", + "name-zar": "" + }, + "name": "" + }, + "data": { + "formats": { + "name-bits-iec": "", + "name-bits-si": "", + "name-bytes-iec": "", + "name-bytes-si": "", + "name-gibibytes": "", + "name-gigabytes": "", + "name-kibibytes": "", + "name-kilobytes": "", + "name-mebibytes": "", + "name-megabytes": "", + "name-pebibytes": "", + "name-petabytes": "", + "name-tebibytes": "", + "name-terabytes": "" + }, + "name": "" + }, + "data-rate": { + "formats": { + "name-bits-sec-iec": "", + "name-bits-sec-si": "", + "name-bytes-sec-iec": "", + "name-bytes-sec-si": "", + "name-gibibits-sec": "", + "name-gibibytes-sec": "", + "name-gigabits-sec": "", + "name-gigabytes-sec": "", + "name-kibibits-sec": "", + "name-kibibytes-sec": "", + "name-kilobits-sec": "", + "name-kilobytes-sec": "", + "name-mebibits-sec": "", + "name-mebibytes-sec": "", + "name-megabits-sec": "", + "name-megabytes-sec": "", + "name-packets-sec": "", + "name-pebibits-sec": "", + "name-pebibytes-sec": "", + "name-petabits-sec": "", + "name-petabytes-sec": "", + "name-tebibits-sec": "", + "name-tebibytes-sec": "", + "name-terabits-sec": "", + "name-terabytes-sec": "" + }, + "name": "" + }, + "date-time": { + "formats": { + "name-datetime-default": "", + "name-datetime-iso": "", + "name-datetime-iso-no-date": "", + "name-datetime-local": "", + "name-datetime-local-no-date": "", + "name-datetime-us": "", + "name-datetime-us-no-date": "", + "name-from-now": "" + }, + "name": "" + }, + "energy": { + "formats": { + "name-ampere": "", + "name-ampere-hour": "", + "name-decibel-milliwatt": "", + "name-electron-volt": "", + "name-farad": "", + "name-femtofarad": "", + "name-gigawatt": "", + "name-henry": "", + "name-joule": "", + "name-kiloampere": "", + "name-kiloampere-hour": "", + "name-kiloohm": "", + "name-kilovolt": "", + "name-kilovolt-ampere": "", + "name-kilovolt-ampere-reactive": "", + "name-kilowatt": "", + "name-kilowatt-hour": "", + "name-kilowatt-min": "", + "name-lumens": "", + "name-megaohm": "", + "name-megawatt": "", + "name-megawatt-hour": "", + "name-microfarad": "", + "name-microhenry": "", + "name-milliampere": "", + "name-milliampere-hour": "", + "name-millihenry": "", + "name-milliohm": "", + "name-millivolt": "", + "name-milliwatt": "", + "name-nanofarad": "", + "name-ohm": "", + "name-picofarad": "", + "name-volt": "", + "name-volt-ampere": "", + "name-volt-ampere-reactive": "", + "name-watt": "", + "name-watt-hour": "", + "name-watt-hour-per-kg": "", + "name-watt-square-meter": "" + }, + "name": "" + }, + "flow": { + "formats": { + "name-cubic-feet-min": "", + "name-cubic-feet-sec": "", + "name-cubic-meters-sec": "", + "name-gallons-min": "", + "name-litre-hour": "", + "name-litre-min": "", + "name-lux": "", + "name-millilitre-min": "" + }, + "name": "" + }, + "force": { + "formats": { + "name-kilonewton-meters": "", + "name-kilonewtons": "", + "name-newton-meters": "", + "name-newtons": "" + }, + "name": "" + }, + "hash-rate": { + "formats": { + "name-exahashes-sec": "", + "name-gigahashes-sec": "", + "name-hashes-sec": "", + "name-kilohashes-sec": "", + "name-megahashes-sec": "", + "name-petahashes-sec": "", + "name-terahashes-sec": "" + }, + "name": "" + }, + "length": { + "formats": { + "name-feet": "", + "name-inch": "", + "name-kilometer": "", + "name-meter": "", + "name-mile": "", + "name-millimeter": "" + }, + "name": "" + }, + "mass": { + "formats": { + "name-gram": "", + "name-kilogram": "", + "name-metric-ton": "", + "name-milligram": "", + "name-pound": "" + }, + "name": "" + }, + "misc": { + "formats": { + "name-candala": "", + "name-decibel": "", + "name-hexadecimal": "", + "name-hexadecimal-0x": "", + "name-humidity": "", + "name-locale": "", + "name-number": "", + "name-percent-1": "", + "name-percent-100": "", + "name-pixels": "", + "name-scientific": "", + "name-short": "", + "name-si-short": "", + "name-string": "" + }, + "name": "" + }, + "pressure": { + "formats": { + "name-bars": "", + "name-hectopascals": "", + "name-inches-mercury": "", + "name-kilobars": "", + "name-kilopascals": "", + "name-millibars": "", + "name-pascals": "", + "name-psi": "" + }, + "name": "" + }, + "radiation": { + "formats": { + "name-becquerel": "", + "name-curie": "", + "name-exposure": "", + "name-gray": "", + "name-microsievert": "", + "name-microsievert-hour": "", + "name-millisievert": "", + "name-millisievert-hour": "", + "name-rad": "", + "name-rem": "", + "name-roentgen": "", + "name-sievert": "", + "name-sievert-hour": "" + }, + "name": "" + }, + "rotational-speed": { + "formats": { + "name-degrees-sec": "", + "name-gigahertz": "", + "name-hertz": "", + "name-kilohertz": "", + "name-megahertz": "", + "name-radians-sec": "", + "name-rpm": "" + }, + "name": "" + }, + "temperature": { + "formats": { + "name-celsius": "", + "name-fahrenheit": "", + "name-kelvin": "" + }, + "name": "" + }, + "throughput": { + "formats": { + "name-counts-min": "", + "name-counts-sec": "", + "name-events-min": "", + "name-events-sec": "", + "name-io-ops-sec": "", + "name-messages-min": "", + "name-messages-sec": "", + "name-ops-min": "", + "name-ops-sec": "", + "name-reads-min": "", + "name-reads-sec": "", + "name-records-min": "", + "name-records-sec": "", + "name-requests-min": "", + "name-requests-sec": "", + "name-rows-min": "", + "name-rows-sec": "", + "name-writes-min": "", + "name-writes-sec": "" + }, + "name": "" + }, + "time": { + "formats": { + "name-clock-ms": "", + "name-clock-s": "", + "name-days": "", + "name-duration-dhms": "", + "name-duration-hms": "", + "name-duration-ms": "", + "name-duration-s": "", + "name-hertz": "", + "name-hours": "", + "name-microseconds": "", + "name-milliseconds": "", + "name-minutes": "", + "name-nanoseconds": "", + "name-seconds": "", + "name-timeticks": "" + }, + "name": "" + }, + "velocity": { + "formats": { + "name-kilometers-hour": "", + "name-knot": "", + "name-meters-second": "", + "name-miles-hour": "" + }, + "name": "" + }, + "volume": { + "formats": { + "name-cubic-decimeter": "", + "name-cubic-meter": "", + "name-gallons": "", + "name-litre": "", + "name-millilitre": "", + "name-normal-cubic-meter": "" + }, + "name": "" + } + } + } + }, "grafana-ui": { "action-editor": { "button": { @@ -7928,6 +8364,60 @@ "previous-label": "上個月", "select-placeholder": "選擇日期/時間" }, + "date-time-pickers": { + "month-options": { + "label-april": "", + "label-august": "", + "label-december": "", + "label-february": "", + "label-january": "", + "label-july": "", + "label-june": "", + "label-march": "", + "label-may": "", + "label-november": "", + "label-october": "", + "label-september": "" + }, + "quick-options": { + "day-before-yesterday": "", + "last-1-hour": "", + "last-1-year": "", + "last-12-hours": "", + "last-15-mins": "", + "last-2-days": "", + "last-2-years": "", + "last-24-hours": "", + "last-3-hours": "", + "last-30-days": "", + "last-30-mins": "", + "last-5-mins": "", + "last-5-years": "", + "last-6-hours": "", + "last-6-months": "", + "last-7-days": "", + "last-90-days": "", + "previous-fiscal-quarter": "", + "previous-fiscal-year": "", + "previous-month": "", + "previous-week": "", + "previous-year": "", + "this-day-last-week": "", + "this-fiscal-quarter": "", + "this-fiscal-quarter-so-far": "", + "this-fiscal-year": "", + "this-fiscal-year-so-far": "", + "this-month": "", + "this-month-so-far": "", + "this-week": "", + "this-week-so-far": "", + "this-year": "", + "this-year-so-far": "", + "today": "", + "today-so-far": "", + "yesterday": "" + } + }, "drawer": { "close": "關閉" }, @@ -7975,6 +8465,20 @@ "field-name-picker": { "placeholder": "選擇欄位" }, + "get-all-field-type-icon-options": { + "label-boolean": "", + "label-enum": "", + "label-number": "", + "label-other": "", + "label-string": "", + "label-time": "", + "label-traces": "" + }, + "get-list-of-query-ref-ids": { + "description": "", + "label": "", + "label-missing-ref-id": "" + }, "name-field-name-by-regex-matcher": "名稱與正則表達式相符的欄位", "name-fields-by-query": "查詢傳回的欄位", "name-fields-with-name": "具有名稱的欄位", @@ -8079,7 +8583,10 @@ "no-data": "" }, "no-values-label": "沒有數值", - "pagination-summary": "{{itemsRangeStart}} - 第 {{displayedEnd}} 列,共 {{numRows}} 列" + "pagination-summary": "{{itemsRangeStart}} - 第 {{displayedEnd}} 列,共 {{numRows}} 列", + "sparkline": { + "no-data": "" + } }, "tags": { "list-label": "標籤" @@ -8804,8 +9311,11 @@ "move-displayed-field-down": "", "move-displayed-field-up": "", "no-details": "沒有要顯示的欄位。", + "open-assistant": "", "pin-line": "固定紀錄", "remove-displayed-field": "", + "remove-log": "", + "scroll-to-logline": "", "search": { "no-results": "沒有要顯示的結果。" }, @@ -8820,6 +9330,9 @@ "copy-log": "複製日誌行", "hide-details": "顯示紀錄詳細資料", "icon-label": "日誌功能表", + "log-line": "", + "log-line-explainer": "", + "open-assistant": "", "pin-to-outline": "釘選日誌", "show-context": "顯示內容", "show-details": "隱藏紀錄詳細資料", @@ -9653,7 +10166,9 @@ "aria-label": "說明" }, "invite-user": { - "invite-new-member-button": "邀請新成員" + "invite-button": "邀請", + "invite-new-member-button": "邀請新成員", + "invite-tooltip": "" }, "item": { "add-bookmark": "新增至書籤", @@ -10408,17 +10923,39 @@ }, "provisioned-resource-preview-banner": { "preview-banner": { + "behind-branch-text": "", "not-saved": "", - "open-pull-request-in-git-hub": "", - "view-pull-request-in-git-hub": "" + "open-in-repo-button": "", + "open-pull-request-in-repo": "", + "view-in-repo-button": "", + "view-pull-request-in-repo": "" }, - "title-created-branch-git-hub": "", - "title-loaded-pull-request-git-hub": "" + "title-created-branch-in-repo": "", + "title-loaded-pull-request-in-repo": "" }, "provisioning": { "banner": { "message": "此功能目前正在積極開發中。為了獲得最佳體驗和最新改進版本,建議使用 Grafana 的<2>夜間版本。" }, + "bitbucket": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "bootstrap-step": { "description-clear-repository-connection": "為此儲存庫連線新增一個明確的名稱", "empty": "空", @@ -10428,7 +10965,9 @@ "label-display-name": "顯示名稱", "placeholder-my-repository-connection": "我的儲存庫連線", "text-loading-resource-information": "正在載入資源資訊…", - "files-count_other": "{{count}} 個檔案" + "dashboards-count_other": "", + "files-count_other": "{{count}} 個檔案", + "folders-count_other": "" }, "check-repository": { "check": "勾選" @@ -10439,36 +10978,20 @@ "button-save": "儲存", "button-saving": "正在儲存…", "description-enabled": "一旦啟用自動拉取,目標便無法變更。", - "description-path": "Git 儲存庫中子目錄的路徑", "description-read-only": "無法透過 Grafana 修改資源。", - "description-repository-url": "輸入 GitHub 儲存庫網址", "description-title": "設定的人類可讀名稱", "error-required": "此為必填欄位。", "error-save-repository": "無法儲存儲存庫設定", - "error-valid-github-url": "請輸入有效的 GitHub 儲存庫網址", "label-automatic-pulling": "自動拉取", - "label-branch": "分支", "label-enabled": "已啟用", - "label-github-token": "GitHub 權杖", "label-interval-seconds": "間隔(秒)", - "label-local-path": "本機路徑", - "label-path": "路徑", - "label-pr-workflow": "儲存時啟用拉取請求選項", "label-repository-type": "儲存庫類型", - "label-repository-url": "儲存庫網址", "label-target": "目標", "label-title": "標題", "option-entire-instance": "整個執行個體", - "option-github": "GitHub", - "option-local": "本機", "option-managed-folder": "受管理的資料夾", - "placeholder-branch": "主要", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo-name", "placeholder-interval-seconds": "60", - "placeholder-local-path": "/path/to/repo", - "placeholder-my-config": "我的設定", - "placeholder-select-repository-type": "選擇儲存庫類型" + "placeholder-my-config": "我的設定" }, "config-form-github-collapse": { "description-realtime-feedback": "<0>設定 webhook,以便在提交變更時在 Grafana 中即時取得更新。在變更上線之前,使用拉取請求來檢查並核准變更。", @@ -10482,27 +11005,8 @@ }, "connect-repository-button": { "configure": "設定", - "configure-file": "設定檔案佈建", - "configure-git-sync": "設定 Git 同步", "repository-limit-info-alert": "已達到儲存庫限制({{count}} 個)" }, - "connect-step": { - "description-branch": "用於 GitHub 儲存庫的分支", - "description-github-path": "這是 GitHub 儲存庫中的子目錄路徑,儀表板將從中儲存和佈建", - "description-paste-your-git-hub-personal-access-token": "貼上您的 GitHub 個人存取權杖", - "description-repository-url": "貼上 GitHub 儲存庫的網址", - "error-field-required": "此為必填欄位。", - "error-invalid-github-url": "請輸入有效的 GitHub 儲存庫網址", - "label-access-token": "GitHub 存取權杖", - "label-branch": "分支名稱", - "label-local-path": "本機路徑", - "label-path": "儲存庫中子目錄的路徑", - "label-repository-url": "Github 儲存庫網址", - "placeholder-branch": "主要", - "placeholder-github-token": "ghp_yourTokenHere1234567890abcdEFGHijklMNOP", - "placeholder-github-url": "https://github.com/username/repo", - "placeholder-local-path": "/path/to/repo" - }, "delete-repository-button": { "button-delete": "刪除", "confirm-delete-repository": "確定要刪除儲存庫設定嗎?", @@ -10534,17 +11038,17 @@ "title-instant-updates-requests-webhooks": "透過 webhook 即時更新和拉取請求。", "title-visual-previews-in-pull-requests": "具有圖片轉譯的拉取請求中的視覺預覽" }, + "error-title-default": "", "expanded-row": { "job-specification": "作業規範", - "summary": "摘要", - "title-error": "錯誤" + "summary": "摘要" }, "features-list": { "actions": { "set-up-required-feature-toggles": "設定所需的功能切換" }, - "manage-dashboards-provision-updates-automatically": "在 GitHub 中將儀表板作為代碼管理,並自動佈建更新", - "manage-your-dashboards-with-remote-provisioning": "開始使用 GitSync", + "manage-dashboards-provision-updates-automatically": "", + "manage-your-dashboards-with-remote-provisioning": "", "store-dashboards-in-version-controlled-storage": "將儀表板儲存在版本控制的儲存空間中,以便更好地組織和追蹤歷史紀錄" }, "file-history-page": { @@ -10565,18 +11069,16 @@ }, "finish-step": { "description-enable-previews": "在拉取請求中新增儀表板變更的圖片預覽。您的 Grafana 儀表板圖像將在您的 Git 儲存庫中共用,並且任何具有儲存庫存取權限者都可以看見。", + "description-generate-dashboard-previews": "", "description-image-rendering": "需要圖片轉譯。<2>設定圖片轉譯", - "description-often-shall-instance-updates-git-hub": "執行個體應多久從 GitHub 拉取一次更新?", - "description-pr-enable-description": "允許使用者選擇在儲存變更時是否開啟拉取請求。如果儲存庫不允許直接變更主分支,則可能仍需要拉取請求。", + "description-preview-requirements": "", "description-read-only": "無法透過 Grafana 修改資源。", - "description-webhooks-enable": "允許使用者選擇在儲存變更時是否開啟拉取請求。如果儲存庫不允許直接變更主分支,則可能仍需要拉取請求。", + "description-sync-interval": "", + "error-sync-interval-required": "", "label-enable-previews": "在拉取請求中啟用儀表板預覽", - "label-pr-workflow": "儲存時啟用拉取請求選項", + "label-generate-dashboard-previews": "", "label-read-only": "唯讀", - "label-update-instance-interval-seconds": "更新執行個體間隔(秒)", - "placeholder": "60", - "text-setup-later": "您可以稍後再設定", - "title-enhance-github": "增強您的 GitHub 體驗" + "label-sync-interval": "" }, "folder-repository-list": { "no-results-matching-your-query": "沒有符合您查詢的結果", @@ -10607,6 +11109,57 @@ "header": "佈建", "subtitle-provisioning-feature": "檢視及管理您的佈建連線" }, + "git": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "token-user-description": "", + "token-user-label": "", + "token-user-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "github": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, + "gitlab": { + "branch-description": "", + "branch-label": "", + "branch-required": "", + "path-description": "", + "path-label": "", + "pr-workflow-description": "", + "pr-workflow-label": "", + "token-description": "", + "token-label": "", + "token-required": "", + "url-description": "", + "url-label": "", + "url-pattern": "", + "url-required": "" + }, "history-view": { "not-found": "無結果" }, @@ -10633,9 +11186,17 @@ "starting": "正在開始…", "status": { "title-error-running-job": "執行作業時發生錯誤", - "title-job-completed-successfully": "作業已成功完成" + "title-warning-running-job": "" }, - "summary": "摘要" + "summary": "摘要", + "title": { + "error-fetching-active-job": "" + } + }, + "local": { + "path-description": "", + "path-label": "", + "path-required": "" }, "mode-options": { "folder": { @@ -10661,7 +11222,7 @@ }, "repository-actions": { "settings": "設定", - "source-code": "原始碼" + "source-code": "" }, "repository-card": { "get-repository-meta": { @@ -10726,6 +11287,24 @@ "title-legacy-storage": "舊版儲存空間", "title-queued-for-deletion": "已排入刪除佇列" }, + "repository-type-cards": { + "choose-provider": "", + "configure-file": "", + "configure-with-provider": "", + "provider-not-listed": "" + }, + "repository-types": { + "bitbucket": "", + "bitbucket-description": "", + "github": "", + "github-description": "", + "gitlab": "", + "gitlab-description": "", + "local": "", + "local-description": "", + "pure-git": "", + "pure-git-description": "" + }, "resource-view": { "base": "基數", "dashboard-preview": "儀表板預覽", @@ -10755,6 +11334,11 @@ "label-current-step": "目前步驟", "label-pending-step": "待處理步驟" }, + "sync-job": { + "error-no-job-id": "", + "error-no-repository-name": "", + "error-starting-job": "" + }, "sync-repository": { "body-edit-configuration": "編輯設定", "button-edit": "編輯", @@ -10765,9 +11349,6 @@ "tooltip-unhealthy-repository": "無法拉取狀態不佳的儲存庫" }, "synchronize-step": { - "error-no-job-id": "無法啟動作業", - "error-no-repository-name": "未提供儲存庫名稱", - "error-starting-job": "開始作業時發生錯誤", "synchronization-description": "包含每個歷史數值的提交", "synchronization-options": "同步選項" }, @@ -10776,6 +11357,7 @@ "go-to": "前往", "make-sure": "請務必包含以下權限" }, + "warning-title-default": "", "wizard": { "alert-point-1": "在此過程中,將無法建立、編輯或刪除資源。在最後一步中,它們將會消失。", "alert-point-2": "佈建完成後,資源將重新出現,並透過外部儲存空間進行管理。", @@ -11837,7 +12419,6 @@ }, "label-apply-to-entire-row": "套用至整列", "label-background-display-mode": "背景顯示模式", - "label-wrap-text": "文字自動換行", "wrap-text": "文字自動換行" }, "column-alignment-options": { diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index af0f1565616..d0ccb095e24 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -4,7 +4,15 @@ import './global-jquery-shim'; import { TransformStream } from 'node:stream/web'; import { TextEncoder, TextDecoder } from 'util'; -import { EventBusSrv } from '@grafana/data'; +// we need to isolate the `@grafana/data` module here now that it depends on `@grafana/i18n` +jest.isolateModulesAsync(async () => { + const { EventBusSrv } = await import('@grafana/data'); + const testAppEvents = new EventBusSrv(); + jest.mock('../app/core/core', () => ({ + ...jest.requireActual('../app/core/core'), + appEvents: testAppEvents, + })); +}); import { GrafanaBootConfig } from '@grafana/runtime'; import 'blob-polyfill'; @@ -14,7 +22,6 @@ import './mocks/workers'; import '../vendor/flot/jquery.flot'; import '../vendor/flot/jquery.flot.time'; -const testAppEvents = new EventBusSrv(); const global = window as any; // mock the default window.grafanaBootData settings @@ -61,11 +68,6 @@ global.TransformStream = TransformStream; // add scrollTo interface since it's not implemented in jsdom Element.prototype.scrollTo = () => {}; -jest.mock('../app/core/core', () => ({ - ...jest.requireActual('../app/core/core'), - appEvents: testAppEvents, -})); - const throwUnhandledRejections = () => { process.on('unhandledRejection', (err) => { throw err; diff --git a/scripts/docs/generate-transformations.ts b/scripts/docs/generate-transformations.ts index a8e52c07fc9..bc13c8cb413 100644 --- a/scripts/docs/generate-transformations.ts +++ b/scripts/docs/generate-transformations.ts @@ -102,6 +102,8 @@ Transformations are a powerful way to manipulate data returned by a query before - Perform mathematical operations across queries - Use the output of one transformation as the input to another transformation +{{< docs/learning-journeys title="Transform data in a Grafana Cloud dashboard" url="https://grafana.com/docs/learning-journeys/data-transformation/" >}} + For users that rely on multiple views of the same dataset, transformations offer an efficient method of creating and maintaining numerous dashboards. You can also use the output of one transformation as the input to another transformation, which results in a performance gain. diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 63175726fd5..746c183767e 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -834,7 +834,7 @@ def playwright_e2e_tests_step(): "commands": [ "npx wait-on@7.0.1 http://$HOST:$PORT", "yarn playwright install --with-deps chromium", - "yarn e2e:playwright --grep @plugins", + "GRAFANA_URL=http://$HOST:$PORT yarn e2e:playwright --grep @plugins", ], } diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index 95dfc640eb3..c14e87833fd 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.1.2" -golang_version = "1.24.4" +golang_version = "1.24.5" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "22.16.0" diff --git a/scripts/go-workspace/go.mod b/scripts/go-workspace/go.mod index 5e23c2f70b2..61239d308b3 100644 --- a/scripts/go-workspace/go.mod +++ b/scripts/go-workspace/go.mod @@ -1,5 +1,5 @@ module github.com/grafana/grafana/scripts/go-workspace -go 1.24.4 +go 1.24.5 require golang.org/x/mod v0.24.0 diff --git a/scripts/modowners/go.mod b/scripts/modowners/go.mod index 10b70c57884..d6dcd567649 100644 --- a/scripts/modowners/go.mod +++ b/scripts/modowners/go.mod @@ -1,5 +1,5 @@ module github.com/grafana/grafana/scripts/modowners -go 1.24.4 +go 1.24.5 require golang.org/x/mod v0.24.0 diff --git a/yarn.lock b/yarn.lock index b5872e86106..4486bdfc0b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,124 +121,124 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.26.2": - version: 7.26.2 - resolution: "@babel/code-frame@npm:7.26.2" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.27.1" js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/db2c2122af79d31ca916755331bb4bac96feb2b334cdaca5097a6b467fdd41963b89b14b6836a14f083de7ff887fc78fa1b3c10b14e743d33e12dbfe5ee3d223 + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.5, @babel/compat-data@npm:^7.26.8": - version: 7.26.8 - resolution: "@babel/compat-data@npm:7.26.8" - checksum: 10/bdddf577f670e0e12996ef37e134856c8061032edb71a13418c3d4dae8135da28910b7cd6dec6e668ab3a41e42089ef7ee9c54ef52fe0860b54cb420b0d14948 +"@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/compat-data@npm:7.28.0" + checksum: 10/1a56a5e48c7259f72cc4329adeca38e72fd650ea09de267ea4aa070e3da91e5c265313b6656823fff77d64a8bab9554f276c66dade9355fdc0d8604deea015aa languageName: node linkType: hard -"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": - version: 7.26.10 - resolution: "@babel/core@npm:7.26.10" +"@babel/core@npm:7.28.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.5, @babel/core@npm:^7.22.9, @babel/core@npm:^7.7.5": + version: 7.28.0 + resolution: "@babel/core@npm:7.28.0" dependencies: "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.10" - "@babel/helper-compilation-targets": "npm:^7.26.5" - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.10" - "@babel/parser": "npm:^7.26.10" - "@babel/template": "npm:^7.26.9" - "@babel/traverse": "npm:^7.26.10" - "@babel/types": "npm:^7.26.10" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.27.3" + "@babel/helpers": "npm:^7.27.6" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/68f6707eebd6bb8beed7ceccf5153e35b86c323e40d11d796d75c626ac8f1cc4e1f795584c5ab5f886bc64150c22d5088123d68c069c63f29984c4fc054d1dab + checksum: 10/1c86eec8d76053f7b1c5f65296d51d7b8ac00f80d169ff76d3cd2e7d85ab222eb100d40cc3314f41b96c8cc06e9abab21c63d246161f0f3f70ef14c958419c33 languageName: node linkType: hard -"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": - version: 7.27.0 - resolution: "@babel/generator@npm:7.27.0" +"@babel/generator@npm:^7.22.5, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.28.0, @babel/generator@npm:^7.7.2": + version: 7.28.0 + resolution: "@babel/generator@npm:7.28.0" dependencies: - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" + "@babel/parser": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10/5447c402b1d841132534a0a9715e89f4f28b6f2886a23e70aaa442150dba4a1e29e4e2351814f439ee1775294dccdef9ab0a4192b6e6a5ad44e24233b3611da2 + checksum: 10/064c5ba4c07ecd7600377bd0022d5f6bdb3b35e9ff78d9378f6bd1e656467ca902c091647222ab2f0d2967f6d6c0ca33157d37dd9b1c51926c9b0e1527ab9b92 languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" +"@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" dependencies: - "@babel/types": "npm:^7.25.9" - checksum: 10/41edda10df1ae106a9b4fe617bf7c6df77db992992afd46192534f5cff29f9e49a303231733782dd65c5f9409714a529f215325569f14282046e9d3b7a1ffb6c + "@babel/types": "npm:^7.27.3" + checksum: 10/63863a5c936ef82b546ca289c9d1b18fabfc24da5c4ee382830b124e2e79b68d626207febc8d4bffc720f50b2ee65691d7d12cc0308679dee2cd6bdc926b7190 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9, @babel/helper-compilation-targets@npm:^7.26.5": - version: 7.26.5 - resolution: "@babel/helper-compilation-targets@npm:7.26.5" +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: - "@babel/compat-data": "npm:^7.26.5" - "@babel/helper-validator-option": "npm:^7.25.9" + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/f3b5f0bfcd7b6adf03be1a494b269782531c6e415afab2b958c077d570371cf1bfe001c442508092c50ed3711475f244c05b8f04457d8dea9c34df2b741522bf + checksum: 10/bd53c30a7477049db04b655d11f4c3500aea3bcbc2497cf02161de2ecf994fec7c098aabbcebe210ffabc2ecbdb1e3ffad23fb4d3f18723b814f423ea1749fe8 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.9" +"@babel/helper-create-class-features-plugin@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-create-class-features-plugin@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-member-expression-to-functions": "npm:^7.25.9" - "@babel/helper-optimise-call-expression": "npm:^7.25.9" - "@babel/helper-replace-supers": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-member-expression-to-functions": "npm:^7.27.1" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/d1d47a7b5fd317c6cb1446b0e4f4892c19ddaa69ea0229f04ba8bea5f273fc8168441e7114ad36ff919f2d310f97310cec51adc79002e22039a7e1640ccaf248 + checksum: 10/701579b49046cd42f6a6b1e693e6827df8623185adf0911c4d68a219a082d8fd4501672880d92b6b96263d1c92a3beb891b3464a662a55e69e7539d8db9277da languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.9" +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - regexpu-core: "npm:^6.1.1" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + regexpu-core: "npm:^6.2.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/bc2b6a365ddf490c416661833dbf4430ae0c66132acccb5ce257e82026dd9db54da788bfbdcb7e0032aa0cba965cb1be169b1e1fb2c8c029b81625da4963f6b9 + checksum: 10/dea272628cd8874f127ab7b2ee468620aabc1383d38bb40c49a9c7667db2258cdfe6620a1d1412f5f0706583f6301b4b7ad3d5932f24df7fe72e66bf9bc0be45 languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.3": - version: 0.6.3 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.3" +"@babel/helper-define-polyfill-provider@npm:^0.6.5": + version: 0.6.5 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.5" dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" + debug: "npm:^4.4.1" lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" + resolve: "npm:^1.22.10" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/b79a77ac8fbf1aaf6c7f99191871760508e87d75a374ff3c39c6599a17d9bb82284797cd451769305764e504546caf22ae63367b22d6e45e32d0a8f4a34aab53 + checksum: 10/0bdd2d9654d2f650c33976caa1a2afac2c23cf07e83856acdb482423c7bf4542c499ca0bdc723f2961bb36883501f09e9f4fe061ba81c07996daacfba82a6f62 languageName: node linkType: hard @@ -252,130 +252,137 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.0.0, @babel/helper-member-expression-to-functions@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" - dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/ef8cc1c1e600b012b312315f843226545a1a89f25d2f474ce2503fd939ca3f8585180f291a3a13efc56cf13eddc1d41a3a040eae9a521838fd59a6d04cc82490 +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-module-imports@npm:7.25.9" +"@babel/helper-member-expression-to-functions@npm:^7.0.0, @babel/helper-member-expression-to-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-member-expression-to-functions@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/e090be5dee94dda6cd769972231b21ddfae988acd76b703a480ac0c96f3334557d70a965bf41245d6ee43891e7571a8b400ccf2b2be5803351375d0f4e5bcf08 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/533a5a2cf1c9a8770d241b86d5f124c88e953c831a359faf1ac7ba1e632749c1748281b83295d227fe6035b202d81f3d3a1ea13891f150c6538e040668d6126a languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/helper-module-transforms@npm:7.26.0" +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/58e792ea5d4ae71676e0d03d9fef33e886a09602addc3bd01388a98d87df9fcfd192968feb40ac4aedb7e287ec3d0c17b33e3ecefe002592041a91d8a1998a8d + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-module-transforms@npm:7.27.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.3" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/9841d2a62f61ad52b66a72d08264f23052d533afc4ce07aec2a6202adac0bfe43014c312f94feacb3291f4c5aafe681955610041ece2c276271adce3f570f2f5 + checksum: 10/47abc90ceb181b4bdea9bf1717adf536d1b5e5acb6f6d8a7a4524080318b5ca8a99e6d58677268c596bad71077d1d98834d2c3815f2443e6d3f287962300f15d languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.0.0, @babel/helper-optimise-call-expression@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-optimise-call-expression@npm:7.25.9" +"@babel/helper-optimise-call-expression@npm:^7.0.0, @babel/helper-optimise-call-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" dependencies: - "@babel/types": "npm:^7.25.9" - checksum: 10/f09d0ad60c0715b9a60c31841b3246b47d67650c512ce85bbe24a3124f1a4d66377df793af393273bc6e1015b0a9c799626c48e53747581c1582b99167cc65dc + "@babel/types": "npm:^7.27.1" + checksum: 10/0fb7ee824a384529d6b74f8a58279f9b56bfe3cce332168067dddeab2552d8eeb56dc8eaf86c04a3a09166a316cb92dfc79c4c623cd034ad4c563952c98b464f languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.26.5 - resolution: "@babel/helper-plugin-utils@npm:7.26.5" - checksum: 10/1cc0fd8514da3bb249bed6c27227696ab5e84289749d7258098701cffc0c599b7f61ec40dd332f8613030564b79899d9826813c96f966330bcfc7145a8377857 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10/96136c2428888e620e2ec493c25888f9ceb4a21099dcf3dd4508ea64b58cdedbd5a9fb6c7b352546de84d6c24edafe482318646932a22c449ebd16d16c22d864 languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-remap-async-to-generator@npm:7.25.9" +"@babel/helper-remap-async-to-generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-wrap-function": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-wrap-function": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/ea37ad9f8f7bcc27c109963b8ebb9d22bac7a5db2a51de199cb560e251d5593fe721e46aab2ca7d3e7a24b0aa4aff0eaf9c7307af9c2fd3a1d84268579073052 + checksum: 10/0747397ba013f87dbf575454a76c18210d61c7c9af0f697546b4bcac670b54ddc156330234407b397f0c948738c304c228e0223039bc45eab4fbf46966a5e8cc languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.0.0, @babel/helper-replace-supers@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-replace-supers@npm:7.25.9" +"@babel/helper-replace-supers@npm:^7.0.0, @babel/helper-replace-supers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-replace-supers@npm:7.27.1" dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.25.9" - "@babel/helper-optimise-call-expression": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-member-expression-to-functions": "npm:^7.27.1" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/8ebf787016953e4479b99007bac735c9c860822fafc51bc3db67bc53814539888797238c81fa8b948b6da897eb7b1c1d4f04df11e501a7f0596b356be02de2ab + checksum: 10/72e3f8bef744c06874206bf0d80a0abbedbda269586966511c2491df4f6bf6d47a94700810c7a6737345a545dfb8295222e1e72f506bcd0b40edb3f594f739ea languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9" +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/fdbb5248932198bc26daa6abf0d2ac42cab9c2dbb75b7e9f40d425c8f28f09620b886d40e7f9e4e08ffc7aaa2cefe6fc2c44be7c20e81f7526634702fb615bdc + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/4f380c5d0e0769fa6942a468b0c2d7c8f0c438f941aaa88f785f8752c103631d0904c7b4e76207a3b0e6588b2dec376595370d92ca8f8f1b422c14a69aa146d4 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-string-parser@npm:7.25.9" - checksum: 10/c28656c52bd48e8c1d9f3e8e68ecafd09d949c57755b0d353739eb4eae7ba4f7e67e92e4036f1cd43378cc1397a2c943ed7bcaf5949b04ab48607def0258b775 +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10/0ae29cc2005084abdae2966afdb86ed14d41c9c37db02c3693d5022fba9f5d59b011d039380b8e537c34daf117c549f52b452398f576e908fb9db3c7abbb3a00 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-identifier@npm:7.25.9" - checksum: 10/3f9b649be0c2fd457fa1957b694b4e69532a668866b8a0d81eabfa34ba16dbf3107b39e0e7144c55c3c652bf773ec816af8df4a61273a2bb4eb3145ca9cf478e +"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.25.9, @babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-option@npm:7.25.9" - checksum: 10/9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10/db73e6a308092531c629ee5de7f0d04390835b21a263be2644276cb27da2384b64676cab9f22cd8d8dbd854c92b1d7d56fc8517cf0070c35d1c14a8c828b0903 languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-wrap-function@npm:7.25.9" +"@babel/helper-wrap-function@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-wrap-function@npm:7.27.1" dependencies: - "@babel/template": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/988dcf49159f1c920d6b9486762a93767a6e84b5e593a6342bc235f3e47cc1cb0c048d8fca531a48143e6b7fce1ff12ddbf735cf5f62cb2f07192cf7c27b89cf + "@babel/template": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/effa5ba1732764982db52295a0003d0d6b527edf70d8c649f5a521808decbc47fc8f3c21cd31f7b6331192289f3bf5617141bce778fec45dcaedf5708d9c3140 languageName: node linkType: hard -"@babel/helpers@npm:^7.26.10": - version: 7.27.0 - resolution: "@babel/helpers@npm:7.27.0" +"@babel/helpers@npm:^7.27.6": + version: 7.27.6 + resolution: "@babel/helpers@npm:7.27.6" dependencies: - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/0dd40ba1e5ba4b72d1763bb381384585a56f21a61a19dc1b9a03381fe8e840207fdaa4da645d14dc028ad768087d41aad46347cc6573bd69d82f597f5a12dc6f + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.27.6" + checksum: 10/33c1ab2b42f05317776a4d67c5b00d916dbecfbde38a9406a1300ad3ad6e0380a2f6fcd3361369119a82a7d3c20de6e66552d147297f17f656cf17912605aa97 languageName: node linkType: hard @@ -391,73 +398,73 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": - version: 7.27.0 - resolution: "@babel/parser@npm:7.27.0" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" dependencies: - "@babel/types": "npm:^7.27.0" + "@babel/types": "npm:^7.28.0" bin: parser: ./bin/babel-parser.js - checksum: 10/0fee9f05c6db753882ca9d10958301493443da9f6986d7020ebd7a696b35886240016899bc0b47d871aea2abcafd64632343719742e87432c8145e0ec2af2a03 + checksum: 10/2c14a0d2600bae9ab81924df0a85bbd34e427caa099c260743f7c6c12b2042e743e776043a0d1a2573229ae648f7e66a80cfb26fc27e2a9eb59b55932d44c817 languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/3c23ef34e3fd7da3578428cb488180ab6b7b96c9c141438374b6d87fa814d87de099f28098e5fc64726c19193a1da397e4d2351d40b459bcd2489993557e2c74 + checksum: 10/fe65257d5b82558bc6bc0f3a5a7a35b4166f71bed3747714dafb6360fadb15f036d568bc1fbeedae819165008c8feb646633ab91c0e3a95284963972f4fa9751 languageName: node linkType: hard -"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.9" +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/d3e14ab1cb9cb50246d20cab9539f2fbd1e7ef1ded73980c8ad7c0561b4d5e0b144d362225f0976d47898e04cbd40f2000e208b0913bd788346cf7791b96af91 + checksum: 10/eb7f4146dc01f1198ce559a90b077e58b951a07521ec414e3c7d4593bf6c4ab5c2af22242a7e9fec085e20299e0ba6ea97f44a45e84ab148141bf9eb959ad25e languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.9" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/a9d1ee3fd100d3eb6799a2f2bbd785296f356c531d75c9369f71541811fa324270258a374db103ce159156d006da2f33370330558d0133e6f7584152c34997ca + checksum: 10/621cfddfcc99a81e74f8b6f9101fd260b27500cb1a568e3ceae9cc8afe9aee45ac3bca3900a2b66c612b1a2366d29ef67d4df5a1c975be727eaad6906f98c2c6 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.25.9" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" - "@babel/plugin-transform-optional-chaining": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.13.0 - checksum: 10/5b298b28e156f64de51cdb03a2c5b80c7f978815ef1026f3ae8b9fc48d28bf0a83817d8fbecb61ef8fb94a7201f62cca5103cc6e7b9e8f28e38f766d7905b378 + checksum: 10/f07aa80272bd7a46b7ba11a4644da6c9b6a5a64e848dfaffdad6f02663adefd512e1aaebe664c4dd95f7ed4f80c872c7f8db8d8e34b47aae0930b412a28711a0 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.9" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/cb893e5deb9312a0120a399835b6614a016c036714de7123c8edabccc56a09c4455016e083c5c4dd485248546d4e5e55fc0e9132b3c3a9bd16abf534138fe3f2 + checksum: 10/dfa68da5f68c0fa9deff1739ac270a5643ea07540b26a2a05403bc536c96595f0fe98a5eac9f9b3501b79ce57caa3045a94c75d5ccbfed946a62469a370ecdc2 languageName: node linkType: hard @@ -537,25 +544,25 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.26.0" +"@babel/plugin-syntax-import-assertions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b58f2306df4a690ca90b763d832ec05202c50af787158ff8b50cdf3354359710bce2e1eb2b5135fcabf284756ac8eadf09ca74764aa7e76d12a5cac5f6b21e67 + checksum: 10/fb661d630808d67ecb85eabad25aac4e9696a20464bad4c4a6a0d3d40e4dc22557d47e9be3d591ec06429cf048cfe169b8891c373606344d51c4f3ac0f91d6d0 languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.26.0" +"@babel/plugin-syntax-import-attributes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c122aa577166c80ee67f75aebebeef4150a132c4d3109d25d7fc058bf802946f883e330f20b78c1d3e3a5ada631c8780c263d2d01b5dbaecc69efefeedd42916 + checksum: 10/97973982fff1bbf86b3d1df13380567042887c50e2ae13a400d02a8ff2c9742a60a75e279bfb73019e1cd9710f04be5e6ab81f896e6678dcfcec8b135e8896cf languageName: node linkType: hard @@ -581,14 +588,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.25.9, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.25.9 - resolution: "@babel/plugin-syntax-jsx@npm:7.25.9" +"@babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/bb609d1ffb50b58f0c1bac8810d0e46a4f6c922aa171c458f3a19d66ee545d36e782d3bffbbc1fed0dc65a558bdce1caf5279316583c0fff5a2c1658982a8563 + checksum: 10/c6d1324cff286a369aa95d99b8abd21dd07821b5d3affd5fe7d6058c84cff9190743287826463ee57a7beecd10fa1e4bc99061df532ee14e188c1c8937b13e3a languageName: node linkType: hard @@ -692,639 +699,653 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.25.9" +"@babel/plugin-transform-arrow-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c29f081224859483accf55fb4d091db2aac0dcd0d7954bac5ca889030cc498d3f771aa20eb2e9cd8310084ec394d85fa084b97faf09298b6bc9541182b3eb5bb + checksum: 10/62c2cc0ae2093336b1aa1376741c5ed245c0987d9e4b4c5313da4a38155509a7098b5acce582b6781cc0699381420010da2e3086353344abe0a6a0ec38961eb7 languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.26.8": - version: 7.26.8 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.26.8" +"@babel/plugin-transform-async-generator-functions@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" - "@babel/helper-remap-async-to-generator": "npm:^7.25.9" - "@babel/traverse": "npm:^7.26.8" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/8fb43823f56281b041dbd358de4f59fccb3e20aac133a439caaeb5aaa30671b3482da9a8515b169fef108148e937c1248b7d6383979c3b30f9348e3fabd29b8e + checksum: 10/8ad31b9969b203dec572738a872e17b14ef76ca45b4ef5ffa76f3514be417ca233d1a0978e5f8de166412a8a745619eb22b07cc5df96f5ebad8ca500f920f61b languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.25.9" +"@babel/plugin-transform-async-to-generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.27.1" dependencies: - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-remap-async-to-generator": "npm:^7.25.9" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-remap-async-to-generator": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b3ad50fb93c171644d501864620ed23952a46648c4df10dc9c62cc9ad08031b66bd272cfdd708faeee07c23b6251b16f29ce0350473e4c79f0c32178d38ce3a6 + checksum: 10/d79d7a7ae7d416f6a48200017d027a6ba94c09c7617eea8b4e9c803630f00094c1a4fc32bf20ce3282567824ce3fcbda51653aac4003c71ea4e681b331338979 languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.26.5": - version: 7.26.5 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.26.5" +"@babel/plugin-transform-block-scoped-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f2046c09bf8e588bfb1a6342d0eee733189102cf663ade27adb0130f3865123af5816b40a55ec8d8fa09271b54dfdaf977cd2f8e0b3dc97f18e690188d5a2174 + checksum: 10/7fb4988ca80cf1fc8345310d5edfe38e86b3a72a302675cdd09404d5064fe1d1fe1283ebe658ad2b71445ecef857bfb29a748064306b5f6c628e0084759c2201 languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-block-scoping@npm:7.25.9" +"@babel/plugin-transform-block-scoping@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-block-scoping@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/89dcdd7edb1e0c2f44e3c568a8ad8202e2574a8a8308248550a9391540bc3f5c9fbd8352c60ae90769d46f58d3ab36f2c3a0fbc1c3620813d92ff6fccdfa79c8 + checksum: 10/eefa0d0b3cd8005b77ad3239700cec90c2b19612e664772c50da6b917b272d20ebc831db6ff0d9fef011a810d9f02c434fdf551b3a4264eb834afa20090a9434 languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-class-properties@npm:7.25.9" +"@babel/plugin-transform-class-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-class-properties@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a8d69e2c285486b63f49193cbcf7a15e1d3a5f632c1c07d7a97f65306df7f554b30270b7378dde143f8b557d1f8f6336c643377943dec8ec405e4cd11e90b9ea + checksum: 10/475a6e5a9454912fe1bdc171941976ca10ea4e707675d671cdb5ce6b6761d84d1791ac61b6bca81a2e5f6430cb7b9d8e4b2392404110e69c28207a754e196294 languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/plugin-transform-class-static-block@npm:7.26.0" +"@babel/plugin-transform-class-static-block@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-class-static-block@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10/60cba3f125a7bc4f90706af0a011697c7ffd2eddfba336ed6f84c5f358c44c3161af18b0202475241a96dee7964d96dd3a342f46dbf85b75b38bb789326e1766 + checksum: 10/2d49de0f5ffc66ae873be1d8c3bf4d22e51889cc779d534e4dbda0f91e36907479e5c650b209fcfc80f922a6c3c2d76c905fc2f5dc78cc9a836f8c31b10686c4 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-classes@npm:7.25.9" +"@babel/plugin-transform-classes@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-classes@npm:7.28.0" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-compilation-targets": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-replace-supers": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" - globals: "npm:^11.1.0" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1914ebe152f35c667fba7bf17ce0d9d0f33df2fb4491990ce9bb1f9ec5ae8cbd11d95b0dc371f7a4cc5e7ce4cf89467c3e34857302911fc6bfb6494a77f7b37e + checksum: 10/1a812a02f641ffc80b139b3c690ceba52476576f9df1a62dbdde9c412e88ca143b7b872da71665838c34276c4ed92f6547199044a424222b84f9a8ee7c85798f languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-computed-properties@npm:7.25.9" +"@babel/plugin-transform-computed-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-computed-properties@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/template": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/aa1a9064d6a9d3b569b8cae6972437315a38a8f6553ee618406da5122500a06c2f20b9fa93aeed04dd895923bf6f529c09fc79d4be987ec41785ceb7d2203122 + checksum: 10/101f6d4575447070943d5a9efaa5bea8c552ea3083d73a9612f1a16d38b0a0a7b79a5feb65c6cc4e4fcabf28e85a570b97ccd3294da966e8fbbb6dfb97220eda languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-destructuring@npm:7.25.9" +"@babel/plugin-transform-destructuring@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-destructuring@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/51b24fbead910ad0547463b2d214dd08076b22a66234b9f878b8bac117603dd23e05090ff86e9ffc373214de23d3e5bf1b095fe54cce2ca16b010264d90cf4f5 + checksum: 10/cddab2520ff32d18005670fc6646396a253d3811d1ccc49f6f858469f05985ee896c346a0cb34d1cf25155c9be76d1068ff878cf8e8459bd3fa27513ec5a6802 languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.25.9" +"@babel/plugin-transform-dotall-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/8bdf1bb9e6e3a2cc8154ae88a3872faa6dc346d6901994505fb43ac85f858728781f1219f40b67f7bb0687c507450236cb7838ac68d457e65637f98500aa161b + checksum: 10/2173e5b13f403538ffc6bd57b190cedf4caf320abc13a99e5b2721864e7148dbd3bd7c82d92377136af80432818f665fdd9a1fd33bc5549a4c91e24e5ce2413c languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.25.9" +"@babel/plugin-transform-duplicate-keys@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/10dbb87bc09582416f9f97ca6c40563655abf33e3fd0fee25eeaeff28e946a06651192112a2bc2b18c314a638fa15c55b8365a677ef67aa490848cefdc57e1d8 + checksum: 10/987b718d2fab7626f61b72325c8121ead42341d6f46ad3a9b5e5f67f3ec558c903f1b8336277ffc43caac504ce00dd23a5456b5d1da23913333e1da77751f08d languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.9" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/f7233cf596be8c6843d31951afaf2464a62a610cb89c72c818c044765827fab78403ab8a7d3a6386f838c8df574668e2a48f6c206b1d7da965aff9c6886cb8e6 + checksum: 10/2a109613535e6ac79240dced71429e988affd6a5b3d0cd0f563c8d6c208c51ce7bf2c300bc1150502376b26a51f279119b3358f1c0f2d2f8abca3bcd62e1ae46 languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.9" +"@babel/plugin-transform-dynamic-import@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/aaca1ccda819be9b2b85af47ba08ddd2210ff2dbea222f26e4cd33f97ab020884bf81a66197e50872721e9daf36ceb5659502c82199884ea74d5d75ecda5c58b + checksum: 10/7a9fbc8d17148b7f11a1d1ca3990d2c2cd44bd08a45dcaf14f20a017721235b9044b20e6168b6940282bb1b48fb78e6afbdfb9dd9d82fde614e15baa7d579932 languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.26.3" +"@babel/plugin-transform-explicit-resource-management@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0d8da2e552a50a775fe8e6e3c32621d20d3c5d1af7ab40ca2f5c7603de057b57b1b5850f74040e4ecbe36c09ac86d92173ad1e223a2a3b3df3cc359ca4349738 + checksum: 10/93d7835160bf8623c7b7072898046c9a2a46cf911f38fa2a002de40a11045a65bf9c1663c98f2e4e04615037f63391832c20b45d7bc26a16d39a97995d0669bc languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.9" +"@babel/plugin-transform-exponentiation-operator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/4dfe8df86c5b1d085d591290874bb2d78a9063090d71567ed657a418010ad333c3f48af2c974b865f53bbb718987a065f89828d43279a7751db1a56c9229078d + checksum: 10/dbbedd24724c2d590ef59d32cb1fef34e99daba41c5b621f9f4c4da23e15c2bb4b1e3d954c314645016391404cf00f1e4ddec8f1f7891438bcde9aaf16e16ee0 languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/plugin-transform-for-of@npm:7.26.9" +"@babel/plugin-transform-export-namespace-from@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/25df1ea3bcecc1bcef99f273fbd8f4a73a509ab7ef3db93629817cb02f9d24868ca3760347f864c8fa4ab79ffa86fb09b2f2de1f2ba1f73f27dbe0c3973c6868 + checksum: 10/85082923eca317094f08f4953d8ea2a6558b3117826c0b740676983902b7236df1f4213ad844cb38c2dae104753dbe8f1cc51f01567835d476d32f5f544a4385 languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-function-name@npm:7.25.9" +"@babel/plugin-transform-for-of@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-for-of@npm:7.27.1" dependencies: - "@babel/helper-compilation-targets": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a8d7c8d019a6eb57eab5ca1be3e3236f175557d55b1f3b11f8ad7999e3fbb1cf37905fd8cb3a349bffb4163a558e9f33b63f631597fdc97c858757deac1b2fd7 + checksum: 10/705c591d17ef263c309bba8c38e20655e8e74ff7fd21883a9cdaf5bf1df42d724383ad3d88ac01f42926e15b1e1e66f2f7f8c4e87de955afffa290d52314b019 languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-json-strings@npm:7.25.9" +"@babel/plugin-transform-function-name@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-function-name@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e2498d84761cfd05aaea53799933d55af309c9d6204e66b38778792d171e4d1311ad34f334259a3aa3407dd0446f6bd3e390a1fcb8ce2e42fe5aabed0e41bee1 + checksum: 10/26a2a183c3c52a96495967420a64afc5a09f743a230272a131668abf23001e393afa6371e6f8e6c60f4182bea210ed31d1caf866452d91009c1daac345a52f23 languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-literals@npm:7.25.9" +"@babel/plugin-transform-json-strings@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-json-strings@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/3cca75823a38aab599bc151b0fa4d816b5e1b62d6e49c156aa90436deb6e13649f5505973151a10418b64f3f9d1c3da53e38a186402e0ed7ad98e482e70c0c14 + checksum: 10/2c05a02f63b49f47069271b3405a66c3c8038de5b995b0700b1bd9a5e2bb3e67abd01e4604629302a521f4d8122a4233944aefa16559fd4373d256cc5d3da57f languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.9" +"@babel/plugin-transform-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-literals@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/8c6febb4ac53852314d28b5e2c23d5dbbff7bf1e57d61f9672e0d97531ef7778b3f0ad698dcf1179f5486e626c77127508916a65eb846a89e98a92f70ed3537b + checksum: 10/0a76d12ab19f32dd139964aea7da48cecdb7de0b75e207e576f0f700121fe92367d788f328bf4fb44b8261a0f605c97b44e62ae61cddbb67b14e94c88b411f95 languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.25.9" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/db92041ae87b8f59f98b50359e0bb172480f6ba22e5e76b13bdfe07122cbf0daa9cd8ad2e78dcb47939938fed88ad57ab5989346f64b3a16953fc73dea3a9b1f + checksum: 10/2757955d81d65cc4701c17b83720745f6858f7a1d1d58117e379c204f47adbeb066b778596b6168bdbf4a22c229aab595d79a9abc261d0c6bfd62d4419466e73 languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-modules-amd@npm:7.25.9" +"@babel/plugin-transform-member-expression-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/75d34c6e709a23bcfa0e06f722c9a72b1d9ac3e7d72a07ef54a943d32f65f97cbbf0e387d874eb9d9b4c8d33045edfa8e8441d0f8794f3c2b9f1d71b928acf2c + checksum: 10/804121430a6dcd431e6ffe99c6d1fbbc44b43478113b79c677629e7f877b4f78a06b69c6bfb2747fd84ee91879fe2eb32e4620b53124603086cf5b727593ebe8 languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.22.5, @babel/plugin-transform-modules-commonjs@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.26.3" +"@babel/plugin-transform-modules-amd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-amd@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f817f02fa04d13f1578f3026239b57f1003bebcf9f9b8d854714bed76a0e4986c79bd6d2e0ac14282c5d309454a8dab683c179709ca753b0152a69c69f3a78e3 + checksum: 10/5ca9257981f2bbddd9dccf9126f1368de1cb335e7a5ff5cca9282266825af5b18b5f06c144320dcf5d2a200d2b53b6d22d9b801a55dc0509ab5a5838af7e61b7 languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.9" +"@babel/plugin-transform-modules-commonjs@npm:^7.22.5, @babel/plugin-transform-modules-commonjs@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/03145aa89b7c867941a03755216cfb503df6d475a78df84849a157fa5f2fcc17ba114a968d0579ae34e7c61403f35d1ba5d188fdfb9ad05f19354eb7605792f9 + checksum: 10/9059243a977bc1f13e3dccfc6feb6508890e7c7bb191f7eb56626b20672b4b12338051ca835ab55426875a473181502c8f35b4df58ba251bef63b25866d995fe languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-modules-umd@npm:7.25.9" +"@babel/plugin-transform-modules-systemjs@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/47d03485fedac828832d9fee33b3b982a6db8197e8651ceb5d001890e276150b5a7ee3e9780749e1ba76453c471af907a159108832c24f93453dd45221788e97 + checksum: 10/06d7bf76ac4688a36ae8e8d2dde1c3b8bab4594362132b74a00d5a32e6716944d68911b9bc53df60e59f4f9c7f1796525503ce3e3eed42f842d7775ccdfd836e languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.25.9" +"@babel/plugin-transform-modules-umd@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-umd@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/7388932863b4ee01f177eb6c2e2df9e2312005e43ada99897624d5565db4b9cef1e30aa7ad2c79bbe5373f284cfcddea98d8fe212714a24c6aba223272163058 + languageName: node + linkType: hard + +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.27.1" + dependencies: + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/434346ba05cf74e3f4704b3bdd439287b95cd2a8676afcdc607810b8c38b6f4798cd69c1419726b2e4c7204e62e4a04d31b0360e91ca57a930521c9211e07789 + checksum: 10/a711c92d9753df26cefc1792481e5cbff4fe4f32b383d76b25e36fa865d8023b1b9aa6338cf18f5c0e864c71a7fbe8115e840872ccd61a914d9953849c68de7d languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-new-target@npm:7.25.9" +"@babel/plugin-transform-new-target@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-new-target@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/07bb3a09028ee7b8e8ede6e6390e3b3aecc5cf9adb2fc5475ff58036c552b8a3f8e63d4c43211a60545f3307cdc15919f0e54cb5455d9546daed162dc54ff94e + checksum: 10/620d78ee476ae70960989e477dc86031ffa3d554b1b1999e6ec95261629f7a13e5a7b98579c63a009f9fdf14def027db57de1f0ae1f06fb6eaed8908ff65cf68 languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.26.6": - version: 7.26.6 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.26.6" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/3832609f043dd1cd8076ab6a00a201573ef3f95bb2144d57787e4a973b3189884c16b4e77ff8e84a6ca47bc3b65bb7df10dca2f6163dfffc316ac96c37b0b5a6 + checksum: 10/15333f4888ffedc449a2a21a0b1ca7983e089f43faa00cfb71d2466e20221a5fd979cdb1a3f57bc20fc62c67bd3ff3dde054133fb6324a58be8f64d20aefacd2 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.9" +"@babel/plugin-transform-numeric-separator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0528ef041ed88e8c3f51624ee87b8182a7f246fe4013f0572788e0727d20795b558f2b82e3989b5dd416cbd339500f0d88857de41b6d3b6fdacb1d5344bcc5b1 + checksum: 10/049b958911de86d32408cd78017940a207e49c054ae9534ab53a32a57122cc592c0aae3c166d6f29bd1a7d75cc779d71883582dd76cb28b2fbb493e842d8ffca languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.9" +"@babel/plugin-transform-object-rest-spread@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.0" dependencies: - "@babel/helper-compilation-targets": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/plugin-transform-parameters": "npm:^7.25.9" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a157ac5af2721090150858f301d9c0a3a0efb8ef66b90fce326d6cc0ae45ab97b6219b3e441bf8d72a2287e95eb04dd6c12544da88ea2345e70b3fac2c0ac9e2 + checksum: 10/55d37dbc0d5d47db860b7cc9fe5e3660d83108113fc3f2a7daecb95c20d4046a70247777969006f7db8fb2005eeeda719b9ff21e9f6d43355d0a62fc41b5880e languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-object-super@npm:7.25.9" +"@babel/plugin-transform-object-super@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-object-super@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-replace-supers": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1817b5d8b80e451ae1ad9080cca884f4f16df75880a158947df76a2ed8ab404d567a7dce71dd8051ef95f90fbe3513154086a32aba55cc76027f6cbabfbd7f98 + checksum: 10/46b819cb9a6cd3cfefe42d07875fee414f18d5e66040366ae856116db560ad4e16f3899a0a7fddd6773e0d1458444f94b208b67c0e3b6977a27ea17a5c13dbf6 languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.9" +"@babel/plugin-transform-optional-catch-binding@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b46a8d1e91829f3db5c252583eb00d05a779b4660abeea5500fda0f8ffa3584fd18299443c22f7fddf0ed9dfdb73c782c43b445dc468d4f89803f2356963b406 + checksum: 10/f4356b04cf21a98480f9788ea50f1f13ee88e89bb6393ba4b84d1f39a4a84c7928c9a4328e8f4c5b6deb218da68a8fd17bf4f46faec7653ddc20ffaaa5ba49f4 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.9" +"@babel/plugin-transform-optional-chaining@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/bc838a499fd9892e163b8bc9bfbc4bf0b28cc3232ee0a6406ae078257c8096518f871d09b4a32c11f4a2d6953c3bc1984619ef748f7ad45aed0b0d9689a8eb36 + checksum: 10/34b0f96400c259a2722740d17a001fe45f78d8ff052c40e29db2e79173be72c1cfe8d9681067e3f5da3989e4a557402df5c982c024c18257587a41e022f95640 languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-parameters@npm:7.25.9" +"@babel/plugin-transform-parameters@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/plugin-transform-parameters@npm:7.27.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/014009a1763deb41fe9f0dbca2c4489ce0ac83dd87395f488492e8eb52399f6c883d5bd591bae3b8836f2460c3937fcebd07e57dce1e0bfe30cdbc63fdfc9d3a + checksum: 10/ba0aa8c977a03bf83030668f64c1d721e4e82d8cce89cdde75a2755862b79dbe9e7f58ca955e68c721fd494d6ee3826e46efad3fbf0855fcc92cb269477b4777 languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-private-methods@npm:7.25.9" +"@babel/plugin-transform-private-methods@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-private-methods@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/6e3671b352c267847c53a170a1937210fa8151764d70d25005e711ef9b21969aaf422acc14f9f7fb86bc0e4ec43e7aefcc0ad9196ae02d262ec10f509f126a58 + checksum: 10/c76f8f6056946466116e67eb9d8014a2d748ade2062636ab82045c1dac9c233aff10e597777bc5af6f26428beb845ceb41b95007abef7d0484da95789da56662 languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.9" +"@babel/plugin-transform-private-property-in-object@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-create-class-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/aa45bb5669b610afa763d774a4b5583bb60ce7d38e4fd2dedfd0703e73e25aa560e6c6124e155aa90b101601743b127d9e5d3eb00989a7e4b4ab9c2eb88475ba + checksum: 10/d4466d42a02c5a318d9d7b8102969fd032b17ff044918dfd462d5cc49bd11f5773ee0794781702afdf4727ba11e9be6cbea1e396bc0a7307761bb9a56399012a languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-property-literals@npm:7.25.9" +"@babel/plugin-transform-property-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-property-literals@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/436046ab07d54a9b44a384eeffec701d4e959a37a7547dda72e069e751ca7ff753d1782a8339e354b97c78a868b49ea97bf41bf5a44c6d7a3c0a05ad40eeb49c + checksum: 10/7caec27d5ed8870895c9faf4f71def72745d69da0d8e77903146a4e135fd7bed5778f5f9cebb36c5fba86338e6194dd67a08c033fc84b4299b7eceab6d9630cb languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-display-name@npm:7.25.9" +"@babel/plugin-transform-react-display-name@npm:^7.27.1": + version: 7.28.0 + resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/dc7affde0ed98e40f629ee92a2fc44fbd8008aabda1ddb3f5bd2632699d3289b08dff65b26cf3b89dab46397ec440f453d19856bbb3a9a83df5b4ac6157c5c39 + checksum: 10/d623644a078086f410b1952429d82c10e2833ebffb97800b25f55ab7f3ffafde34e57a4a71958da73f4abfcef39b598e2ca172f2b43531f98b3f12e0de17c219 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.25.9" +"@babel/plugin-transform-react-jsx-development@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-development@npm:7.27.1" dependencies: - "@babel/plugin-transform-react-jsx": "npm:^7.25.9" + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/537d38369537f1eb56041c4b770bc0733fde1801a7f5ffef40a1217ea448f33ee2fa8e6098a58a82fd00e432c1b9426a66849496da419020c9eca3b1b1a23779 + checksum: 10/b88865d5b8c018992f2332da939faa15c4d4a864c9435a5937beaff3fe43781432cc42e0a5d5631098e0bd4066fc33f5fa72203b388b074c3545fe7aaa21e474 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-jsx@npm:7.25.9" +"@babel/plugin-transform-react-jsx@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/plugin-syntax-jsx": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/eb179ecdf0ae19aed254105cf78fbac35f9983f51ed04b7b67c863a4820a70a879bd5da250ac518321f86df20eac010e53e3411c8750c386d51da30e4814bfb6 + checksum: 10/e865f194770906398957df23530af9a46009ac3737aaa10026b3925fe0a38fc3254f4b227d3b8807ab66ac92c14323bef561dd2217644052de5a9702af76e2f6 languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.25.9" +"@babel/plugin-transform-react-pure-annotations@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9995c0fc7c25d3aaaa0ce84233de02eab2564ea111d0813ec5baa538eb21520402879cc787ad1ad4c2061b99cebc3beb09910e64c9592e8ccb42ae62d9e4fd9a + checksum: 10/a6f591c5e85a1ab0685d4a25afe591fe8d11dc0b73c677cf9560ff8d540d036a1cce9efcb729fc9092def4d854dc304ffdc063a89a9247900b69c516bf971a4c languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-regenerator@npm:7.25.9" +"@babel/plugin-transform-regenerator@npm:^7.28.0": + version: 7.28.1 + resolution: "@babel/plugin-transform-regenerator@npm:7.28.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - regenerator-transform: "npm:^0.15.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1c09e8087b476c5967282c9790fb8710e065eda77c60f6cb5da541edd59ded9d003d96f8ef640928faab4a0b35bf997673499a194973da4f0c97f0935807a482 + checksum: 10/45e3a63bf28d74db4f74d8685d1e416a56f3b7ccf11b13b45589675caf2e7e04d908bdb66bd2407336cd8dfe2ee9013c3bafa46bdddb5ff3248fd64890c36305 languageName: node linkType: hard -"@babel/plugin-transform-regexp-modifiers@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.26.0" +"@babel/plugin-transform-regexp-modifiers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/726deca486bbd4b176f8a966eb0f4aabc19d9def3b8dabb8b3a656778eca0df1fda3f3c92b213aa5a184232fdafd5b7bd73b4e24ca4345c498ef6baff2bda4e1 + checksum: 10/f6cb385fe0e798bff7e9b20cf5912bf40e180895ff3610b1ccdce260f3c20daaebb3a99dc087c8168a99151cd3e16b94f4689fd5a4b01cf1834b45c133e620b2 languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-reserved-words@npm:7.25.9" +"@babel/plugin-transform-reserved-words@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-reserved-words@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/8beda04481b25767acbd1f6b9ef7b3a9c12fbd9dcb24df45a6ad120e1dc4b247c073db60ac742f9093657d6d8c050501fc0606af042f81a3bb6a3ff862cddc47 + checksum: 10/dea0b66742d2863b369c06c053e11e15ba785892ea19cccf7aef3c1bdaa38b6ab082e19984c5ea7810d275d9445c5400fcc385ad71ce707ed9256fadb102af3b languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.25.9" +"@babel/plugin-transform-shorthand-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f774995d58d4e3a992b732cf3a9b8823552d471040e280264dd15e0735433d51b468fef04d75853d061309389c66bda10ce1b298297ce83999220eb0ad62741d + checksum: 10/fbba6e2aef0b69681acb68202aa249c0598e470cc0853d7ff5bd0171fd6a7ec31d77cfabcce9df6360fc8349eded7e4a65218c32551bd3fc0caaa1ac899ac6d4 languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-spread@npm:7.25.9" +"@babel/plugin-transform-spread@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-spread@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/fe72c6545267176cdc9b6f32f30f9ced37c1cafa1290e4436b83b8f377b4f1c175dad404228c96e3efdec75da692f15bfb9db2108fcd9ad260bc9968778ee41e + checksum: 10/3edd28b07e1951f32aa2d380d9a0e0ed408c64a5cea2921d02308541042aca18f146b3a61e82e534d4d61cb3225dbc847f4f063aedfff6230b1a41282e95e8a2 languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.25.9" +"@babel/plugin-transform-sticky-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/7454b00844dbe924030dd15e2b3615b36e196500c4c47e98dabc6b37a054c5b1038ecd437e910aabf0e43bf56b973cb148d3437d50f6e2332d8309568e3e979b + checksum: 10/e1414a502efba92c7974681767e365a8cda6c5e9e5f33472a9eaa0ce2e75cea0a9bef881ff8dda37c7810ad902f98d3c00ead92a3ac3b73a79d011df85b5a189 languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.26.8": - version: 7.26.8 - resolution: "@babel/plugin-transform-template-literals@npm:7.26.8" +"@babel/plugin-transform-template-literals@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/65874c8844ce906507cd5b9c78950d6173f8339b6416a2a9e763021db5a7045315a6f0e58976ec4af5e960c003ef322576c105130a644addb8f94d1a0821a972 + checksum: 10/93aad782503b691faef7c0893372d5243df3219b07f1f22cfc32c104af6a2e7acd6102c128439eab15336d048f1b214ca134b87b0630d8cd568bf447f78b25ce languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.26.7": - version: 7.26.7 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.26.7" +"@babel/plugin-transform-typeof-symbol@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.26.5" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c4ed244c9f252f941f4dff4b6ad06f6d6f5860e9aa5a6cccb5725ead670f2dab58bba4bad9c2b7bd25685e5205fde810857df964d417072c5c282bbfa4f6bf7a + checksum: 10/812d736402a6f9313b86b8adf36740394400be7a09c48e51ee45ab4a383a3f46fc618d656dd12e44934665e42ae71cf143e25b95491b699ef7c737950dbdb862 languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.25.9" +"@babel/plugin-transform-unicode-escapes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f138cbee539963fb3da13f684e6f33c9f7495220369ae12a682b358f1e25ac68936825562c38eae87f01ac9992b2129208b35ec18533567fc805ce5ed0ffd775 + checksum: 10/87b9e49dee4ab6e78f4cdcdbdd837d7784f02868a96bfc206c8dbb17dd85db161b5a0ecbe95b19a42e8aea0ce57e80249e1facbf9221d7f4114d52c3b9136c9e languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.25.9" +"@babel/plugin-transform-unicode-property-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/201f6f46c1beb399e79aa208b94c5d54412047511795ce1e790edcd189cef73752e6a099fdfc01b3ad12205f139ae344143b62f21f44bbe02338a95e8506a911 + checksum: 10/5d99c89537d1ebaac3f526c04b162cf95a47d363d4829f78c6701a2c06ab78a48da66a94f853f85f44a3d72153410ba923e072bed4b7166fa097f503eb14131d languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.25.9" +"@babel/plugin-transform-unicode-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e8baae867526e179467c6ef5280d70390fa7388f8763a19a27c21302dd59b121032568be080749514b097097ceb9af716bf4b90638f1b3cf689aa837ba20150f + checksum: 10/a34d89a2b75fb78e66d97c3dc90d4877f7e31f43316b52176f95a5dee20e9bb56ecf158eafc42a001676ddf7b393d9e67650bad6b32f5405780f25fb83cd68e3 languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.9" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/4445ef20de687cb4dcc95169742a8d9013d680aa5eee9186d8e25875bbfa7ee5e2de26a91177ccf70b1db518e36886abcd44750d28db5d7a9539f0efa6839f4b + checksum: 10/295126074c7388ab05c82ef3ed0907a1ee4666bbdd763477ead9aba6eb2c74bdf65669416861ac93d337a4a27640963bb214acadc2697275ce95aab14868d57f languageName: node linkType: hard @@ -1338,82 +1359,83 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.26.9": - version: 7.26.9 - resolution: "@babel/preset-env@npm:7.26.9" +"@babel/preset-env@npm:7.28.0": + version: 7.28.0 + resolution: "@babel/preset-env@npm:7.28.0" dependencies: - "@babel/compat-data": "npm:^7.26.8" - "@babel/helper-compilation-targets": "npm:^7.26.5" - "@babel/helper-plugin-utils": "npm:^7.26.5" - "@babel/helper-validator-option": "npm:^7.25.9" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.9" - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.9" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.9" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.25.9" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.9" + "@babel/compat-data": "npm:^7.28.0" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.27.1" + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.27.1" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions": "npm:^7.26.0" - "@babel/plugin-syntax-import-attributes": "npm:^7.26.0" + "@babel/plugin-syntax-import-assertions": "npm:^7.27.1" + "@babel/plugin-syntax-import-attributes": "npm:^7.27.1" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.25.9" - "@babel/plugin-transform-async-generator-functions": "npm:^7.26.8" - "@babel/plugin-transform-async-to-generator": "npm:^7.25.9" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.26.5" - "@babel/plugin-transform-block-scoping": "npm:^7.25.9" - "@babel/plugin-transform-class-properties": "npm:^7.25.9" - "@babel/plugin-transform-class-static-block": "npm:^7.26.0" - "@babel/plugin-transform-classes": "npm:^7.25.9" - "@babel/plugin-transform-computed-properties": "npm:^7.25.9" - "@babel/plugin-transform-destructuring": "npm:^7.25.9" - "@babel/plugin-transform-dotall-regex": "npm:^7.25.9" - "@babel/plugin-transform-duplicate-keys": "npm:^7.25.9" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.9" - "@babel/plugin-transform-dynamic-import": "npm:^7.25.9" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.26.3" - "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9" - "@babel/plugin-transform-for-of": "npm:^7.26.9" - "@babel/plugin-transform-function-name": "npm:^7.25.9" - "@babel/plugin-transform-json-strings": "npm:^7.25.9" - "@babel/plugin-transform-literals": "npm:^7.25.9" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.9" - "@babel/plugin-transform-member-expression-literals": "npm:^7.25.9" - "@babel/plugin-transform-modules-amd": "npm:^7.25.9" - "@babel/plugin-transform-modules-commonjs": "npm:^7.26.3" - "@babel/plugin-transform-modules-systemjs": "npm:^7.25.9" - "@babel/plugin-transform-modules-umd": "npm:^7.25.9" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.25.9" - "@babel/plugin-transform-new-target": "npm:^7.25.9" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.26.6" - "@babel/plugin-transform-numeric-separator": "npm:^7.25.9" - "@babel/plugin-transform-object-rest-spread": "npm:^7.25.9" - "@babel/plugin-transform-object-super": "npm:^7.25.9" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.9" - "@babel/plugin-transform-optional-chaining": "npm:^7.25.9" - "@babel/plugin-transform-parameters": "npm:^7.25.9" - "@babel/plugin-transform-private-methods": "npm:^7.25.9" - "@babel/plugin-transform-private-property-in-object": "npm:^7.25.9" - "@babel/plugin-transform-property-literals": "npm:^7.25.9" - "@babel/plugin-transform-regenerator": "npm:^7.25.9" - "@babel/plugin-transform-regexp-modifiers": "npm:^7.26.0" - "@babel/plugin-transform-reserved-words": "npm:^7.25.9" - "@babel/plugin-transform-shorthand-properties": "npm:^7.25.9" - "@babel/plugin-transform-spread": "npm:^7.25.9" - "@babel/plugin-transform-sticky-regex": "npm:^7.25.9" - "@babel/plugin-transform-template-literals": "npm:^7.26.8" - "@babel/plugin-transform-typeof-symbol": "npm:^7.26.7" - "@babel/plugin-transform-unicode-escapes": "npm:^7.25.9" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.25.9" - "@babel/plugin-transform-unicode-regex": "npm:^7.25.9" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.9" + "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" + "@babel/plugin-transform-async-generator-functions": "npm:^7.28.0" + "@babel/plugin-transform-async-to-generator": "npm:^7.27.1" + "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" + "@babel/plugin-transform-block-scoping": "npm:^7.28.0" + "@babel/plugin-transform-class-properties": "npm:^7.27.1" + "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-classes": "npm:^7.28.0" + "@babel/plugin-transform-computed-properties": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" + "@babel/plugin-transform-dotall-regex": "npm:^7.27.1" + "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.27.1" + "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" + "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.0" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.27.1" + "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" + "@babel/plugin-transform-for-of": "npm:^7.27.1" + "@babel/plugin-transform-function-name": "npm:^7.27.1" + "@babel/plugin-transform-json-strings": "npm:^7.27.1" + "@babel/plugin-transform-literals": "npm:^7.27.1" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.27.1" + "@babel/plugin-transform-member-expression-literals": "npm:^7.27.1" + "@babel/plugin-transform-modules-amd": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-modules-systemjs": "npm:^7.27.1" + "@babel/plugin-transform-modules-umd": "npm:^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.27.1" + "@babel/plugin-transform-new-target": "npm:^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.27.1" + "@babel/plugin-transform-numeric-separator": "npm:^7.27.1" + "@babel/plugin-transform-object-rest-spread": "npm:^7.28.0" + "@babel/plugin-transform-object-super": "npm:^7.27.1" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/plugin-transform-private-methods": "npm:^7.27.1" + "@babel/plugin-transform-private-property-in-object": "npm:^7.27.1" + "@babel/plugin-transform-property-literals": "npm:^7.27.1" + "@babel/plugin-transform-regenerator": "npm:^7.28.0" + "@babel/plugin-transform-regexp-modifiers": "npm:^7.27.1" + "@babel/plugin-transform-reserved-words": "npm:^7.27.1" + "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" + "@babel/plugin-transform-spread": "npm:^7.27.1" + "@babel/plugin-transform-sticky-regex": "npm:^7.27.1" + "@babel/plugin-transform-template-literals": "npm:^7.27.1" + "@babel/plugin-transform-typeof-symbol": "npm:^7.27.1" + "@babel/plugin-transform-unicode-escapes": "npm:^7.27.1" + "@babel/plugin-transform-unicode-property-regex": "npm:^7.27.1" + "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.27.1" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.11.0" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.40.0" + babel-plugin-polyfill-corejs2: "npm:^0.4.14" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ac6fad331760c0bc25ed428b7696b297bad7046a75f30e544b392acfb33709f12316b9a5b0c8606f933d5756e1b9d527b46fda09693db52e851325443dd6a574 + checksum: 10/8814453ffe4cfd5926cf2af0ecc956240bcc1e5f49592015962a5f1c115c5c0c34c1e0a5c66d3d4e1a283644bb5ea4e199ced0b6117ffd20113a994fd3080798 languageName: node linkType: hard @@ -1430,19 +1452,19 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:7.26.3": - version: 7.26.3 - resolution: "@babel/preset-react@npm:7.26.3" +"@babel/preset-react@npm:7.27.1": + version: 7.27.1 + resolution: "@babel/preset-react@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-validator-option": "npm:^7.25.9" - "@babel/plugin-transform-react-display-name": "npm:^7.25.9" - "@babel/plugin-transform-react-jsx": "npm:^7.25.9" - "@babel/plugin-transform-react-jsx-development": "npm:^7.25.9" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-transform-react-display-name": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/88cb78c402b79f32389ee06451da51698d5b1da7641d9a47482883f537fe5441a138bd4c077d8533fd6d557406b08911c47b94402cea843db598e020bdd9a373 + checksum: 10/26dd63164ada235ddca53c074944f52cea9a6d8064d02871cad672fe92cc2e136dd1809fb61fa0313a3c19d8e32a00a667d0cbd79465ad8460e2c1b88e5509ae languageName: node linkType: hard @@ -1455,55 +1477,46 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.27.0": - version: 7.27.0 - resolution: "@babel/runtime@npm:7.27.0" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/e6966e03b695feb4c0ac0856a4355231c2580bf9ebd0298f47739f85c0ea658679dd84409daf26378d42c86c1cbe7e33feab709b14e784254b6c441d91606465 +"@babel/runtime@npm:7.28.2, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.7, @babel/runtime@npm:^7.27.0, @babel/runtime@npm:^7.27.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.7": + version: 7.28.2 + resolution: "@babel/runtime@npm:7.28.2" + checksum: 10/a0965fbdd6aaa40709290923bbe05e1c4314021f0cef608eb1d69f04f717c41829e50a53d79c4a0f461512b4be9b3c0190dc19387b219bcdaacdd793b2fe1b8a languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.26.7, @babel/runtime@npm:^7.27.0, @babel/runtime@npm:^7.27.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": - version: 7.27.6 - resolution: "@babel/runtime@npm:7.27.6" - checksum: 10/cc957a12ba3781241b83d528eb69ddeb86ca5ac43179a825e83aa81263a6b3eb88c57bed8a937cdeacfc3192e07ec24c73acdfea4507d0c0428c8e23d6322bfe +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10/fed15a84beb0b9340e5f81566600dbee5eccd92e4b9cc42a944359b1aa1082373391d9d5fc3656981dff27233ec935d0bc96453cf507f60a4b079463999244d8 languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": - version: 7.27.0 - resolution: "@babel/template@npm:7.27.0" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/traverse@npm:7.28.0" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/7159ca1daea287ad34676d45a7146675444d42c7664aca3e617abc9b1d9548c8f377f35a36bb34cf956e1d3610dcb7acfcfe890aebf81880d35f91a7bd273ee5 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.26.8": - version: 7.27.0 - resolution: "@babel/traverse@npm:7.27.0" - dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.27.0" - "@babel/parser": "npm:^7.27.0" - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.0" debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/b0675bc16bd87187e8b090557b0650135de56a621692ad8614b20f32621350ae0fc2e1129b73b780d64a9ed4beab46849a17f90d5267b6ae6ce09ec8412a12c7 + checksum: 10/c1c24b12b6cb46241ec5d11ddbd2989d6955c282715cbd8ee91a09fe156b3bdb0b88353ac33329c2992113e3dfb5198f616c834f8805bb3fa85da1f864bec5f3 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.27.0 - resolution: "@babel/types@npm:7.27.0" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.28.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.28.1 + resolution: "@babel/types@npm:7.28.1" dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/2c322bce107c8a534dc4a23be60d570e6a4cc7ca2e44d4f0eee08c0b626104eb7e60ab8de03463bc5da1773a2f69f1e6edec1648d648d65461d6520a7f3b0770 + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10/b35b0c030326e45efd4ebd87f30a7e5463f0c78617661ff12e8deb3fe983c53c48696374434ffd3664681cbc5b1495ebc69043753b232193e8dc02d1ae7d0ff5 languageName: node linkType: hard @@ -1683,11 +1696,11 @@ __metadata: linkType: hard "@crowdin/crowdin-api-client@npm:^1.42.0": - version: 1.42.0 - resolution: "@crowdin/crowdin-api-client@npm:1.42.0" + version: 1.46.0 + resolution: "@crowdin/crowdin-api-client@npm:1.46.0" dependencies: axios: "npm:^1" - checksum: 10/9e816c5d1fe6f93c0f20625f377f0caa1c2d0cc2a5df97440fc3494ab1d3a7a29d6fe40af8fae32ea96f2bc7fa2bcdc1c7af6dc6563d8fdf86624dc7885e98ff + checksum: 10/a9b783e7deb78970a79ee3f45f511748694cc9bda4a5f6a645bc041657263cf26f9b0c85b5bb0ea16bff3c66e1969cff40a482405f4f79f0edd4ad15bbd0c945 languageName: node linkType: hard @@ -2058,184 +2071,184 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/aix-ppc64@npm:0.25.6" +"@esbuild/aix-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/aix-ppc64@npm:0.25.8" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/android-arm64@npm:0.25.6" +"@esbuild/android-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm64@npm:0.25.8" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/android-arm@npm:0.25.6" +"@esbuild/android-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm@npm:0.25.8" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/android-x64@npm:0.25.6" +"@esbuild/android-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-x64@npm:0.25.8" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/darwin-arm64@npm:0.25.6" +"@esbuild/darwin-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-arm64@npm:0.25.8" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/darwin-x64@npm:0.25.6" +"@esbuild/darwin-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-x64@npm:0.25.8" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/freebsd-arm64@npm:0.25.6" +"@esbuild/freebsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-arm64@npm:0.25.8" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/freebsd-x64@npm:0.25.6" +"@esbuild/freebsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-x64@npm:0.25.8" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-arm64@npm:0.25.6" +"@esbuild/linux-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm64@npm:0.25.8" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-arm@npm:0.25.6" +"@esbuild/linux-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm@npm:0.25.8" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-ia32@npm:0.25.6" +"@esbuild/linux-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ia32@npm:0.25.8" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-loong64@npm:0.25.6" +"@esbuild/linux-loong64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-loong64@npm:0.25.8" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-mips64el@npm:0.25.6" +"@esbuild/linux-mips64el@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-mips64el@npm:0.25.8" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-ppc64@npm:0.25.6" +"@esbuild/linux-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ppc64@npm:0.25.8" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-riscv64@npm:0.25.6" +"@esbuild/linux-riscv64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-riscv64@npm:0.25.8" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-s390x@npm:0.25.6" +"@esbuild/linux-s390x@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-s390x@npm:0.25.8" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/linux-x64@npm:0.25.6" +"@esbuild/linux-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-x64@npm:0.25.8" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/netbsd-arm64@npm:0.25.6" +"@esbuild/netbsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/netbsd-arm64@npm:0.25.8" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/netbsd-x64@npm:0.25.6" +"@esbuild/netbsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/netbsd-x64@npm:0.25.8" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/openbsd-arm64@npm:0.25.6" +"@esbuild/openbsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openbsd-arm64@npm:0.25.8" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/openbsd-x64@npm:0.25.6" +"@esbuild/openbsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openbsd-x64@npm:0.25.8" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/openharmony-arm64@npm:0.25.6" +"@esbuild/openharmony-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openharmony-arm64@npm:0.25.8" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/sunos-x64@npm:0.25.6" +"@esbuild/sunos-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/sunos-x64@npm:0.25.8" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/win32-arm64@npm:0.25.6" +"@esbuild/win32-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-arm64@npm:0.25.8" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/win32-ia32@npm:0.25.6" +"@esbuild/win32-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-ia32@npm:0.25.8" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.6": - version: 0.25.6 - resolution: "@esbuild/win32-x64@npm:0.25.6" +"@esbuild/win32-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-x64@npm:0.25.8" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -2258,29 +2271,36 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.19.0": - version: 0.19.1 - resolution: "@eslint/config-array@npm:0.19.1" +"@eslint/config-array@npm:^0.21.0": + version: 0.21.0 + resolution: "@eslint/config-array@npm:0.21.0" dependencies: - "@eslint/object-schema": "npm:^2.1.5" + "@eslint/object-schema": "npm:^2.1.6" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10/1243b01f463de85c970c18f0994f9d1850dafe8cc8c910edb64105d845edd3cacaa0bbf028bf35a6daaf5a179021140b6a8b1dc7a2f915b42c2d35f022a9c201 + checksum: 10/f5a499e074ecf4b4a5efdca655418a12079d024b77d02fd35868eeb717c5bfdd8e32c6e8e1dd125330233a878026edda8062b13b4310169ba5bfee9623a67aa0 languageName: node linkType: hard -"@eslint/core@npm:^0.10.0": - version: 0.10.0 - resolution: "@eslint/core@npm:0.10.0" +"@eslint/config-helpers@npm:^0.3.0": + version: 0.3.0 + resolution: "@eslint/config-helpers@npm:0.3.0" + checksum: 10/b4c188f28cb8b76d4f4b49566ec1cc9d561bc888ef66ad34587151a212ff168afcf163493c72033149181f947cb950c3cca1525d7486303aae4dfde3e5399573 + languageName: node + linkType: hard + +"@eslint/core@npm:^0.15.0, @eslint/core@npm:^0.15.1": + version: 0.15.1 + resolution: "@eslint/core@npm:0.15.1" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10/de41d7fa5dc468b70fb15c72829096939fc0217c41b8519af4620bc1089cb42539a15325c4c3ee3832facac1836c8c944c4a0c4d0cc8b33ffd8e95962278ae14 + checksum: 10/f00062f0f18fbbfcf080315532340b01e18b729277245899844adb5bec3c9fe2991e1f134c633a15fdfbc4e8b631c2df167d241c49b37e02e937f8c22edfcd3a languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.2.0": - version: 3.2.0 - resolution: "@eslint/eslintrc@npm:3.2.0" +"@eslint/eslintrc@npm:^3.3.1": + version: 3.3.1 + resolution: "@eslint/eslintrc@npm:3.3.1" dependencies: ajv: "npm:^6.12.4" debug: "npm:^4.3.2" @@ -2291,31 +2311,31 @@ __metadata: js-yaml: "npm:^4.1.0" minimatch: "npm:^3.1.2" strip-json-comments: "npm:^3.1.1" - checksum: 10/b32dd90ce7da68e89b88cd729db46b27aac79a2e6cb1fa75d25a6b766d586b443bfbf59622489efbd3c6f696f147b51111e81ec7cd23d70f215c5d474cad0261 + checksum: 10/cc240addbab3c5fceaa65b2c8d5d4fd77ddbbf472c2f74f0270b9d33263dc9116840b6099c46b64c9680301146250439b044ed79278a1bcc557da412a4e3c1bb languageName: node linkType: hard -"@eslint/js@npm:9.19.0": - version: 9.19.0 - resolution: "@eslint/js@npm:9.19.0" - checksum: 10/d8133a83330676d5f0827713af2e9bbf35530631a93520fb59ead6b827a325c54fdd7ad99f2158f895fb393c47bbc55dfdaa945998a647f3b9230f1d5324a626 +"@eslint/js@npm:9.32.0": + version: 9.32.0 + resolution: "@eslint/js@npm:9.32.0" + checksum: 10/a833083a74ed99486c9b72f9be3497ca744692feca12ade7e32119e4b29aba21592055422589a282ed64c46e86b595f147a5270011131a4ea5a2628892bfaf1d languageName: node linkType: hard -"@eslint/object-schema@npm:^2.1.5": - version: 2.1.5 - resolution: "@eslint/object-schema@npm:2.1.5" - checksum: 10/bb07ec53357047f20de923bcd61f0306d9eee83ef41daa32e633e154a44796b5bd94670169eccb8fd8cb4ff42228a43b86953a6321f789f98194baba8207b640 +"@eslint/object-schema@npm:^2.1.6": + version: 2.1.6 + resolution: "@eslint/object-schema@npm:2.1.6" + checksum: 10/266085c8d3fa6cd99457fb6350dffb8ee39db9c6baf28dc2b86576657373c92a568aec4bae7d142978e798b74c271696672e103202d47a0c148da39154351ed6 languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.5": - version: 0.2.5 - resolution: "@eslint/plugin-kit@npm:0.2.5" +"@eslint/plugin-kit@npm:^0.3.4": + version: 0.3.4 + resolution: "@eslint/plugin-kit@npm:0.3.4" dependencies: - "@eslint/core": "npm:^0.10.0" + "@eslint/core": "npm:^0.15.1" levn: "npm:^0.4.1" - checksum: 10/82d0142bc7054587bde4f75c2c517f477df7c320e4bdb47a4d5f766899a313ce65e9ce5d59428178d0be473a95292065053f69637042546b811ad89079781cbc + checksum: 10/9d22a43cbca18e04e818189b63ffabe9128aeea1cf820ffce1e1bcf6446b93778102afc61aff485213eb9bef5b104aad6100b9c9245c28bba566405353377da2 languageName: node linkType: hard @@ -2327,9 +2347,9 @@ __metadata: linkType: hard "@faker-js/faker@npm:^9.0.0, @faker-js/faker@npm:^9.8.0": - version: 9.8.0 - resolution: "@faker-js/faker@npm:9.8.0" - checksum: 10/d7d7940ba7f2bd0d266d507bb7a33ec5099654da624dc8647d7cfd2e908b75ba06ed7dd0ec3b771a18600a734f420370b3cecd1ee5d4799993b1ee3505f866d8 + version: 9.9.0 + resolution: "@faker-js/faker@npm:9.9.0" + checksum: 10/b24b1be0fb3090d54abaaa3a814f37d1a7551f1207dcd330a1af2c70f6312a8b95ebb82653577757dd62f4cbbb56caa3c83513053253654dc3e286a05040ecbe languageName: node linkType: hard @@ -2373,9 +2393,9 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:0.27.13": - version: 0.27.13 - resolution: "@floating-ui/react@npm:0.27.13" +"@floating-ui/react@npm:0.27.14": + version: 0.27.14 + resolution: "@floating-ui/react@npm:0.27.14" dependencies: "@floating-ui/react-dom": "npm:^2.1.4" "@floating-ui/utils": "npm:^0.2.10" @@ -2383,7 +2403,7 @@ __metadata: peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/84a5247e74b7666c069680e8df69e0dcdbf846f6192760c1ec8c53361b2e316895bb43847fba92f6c82e563db45794aeb8755560e769c610ef4a1b956a52d105 + checksum: 10/5466938bdd0db8f7939a640da459bfe8417388d95b1547c69a236f87e4b218992f4a2d7cee14642a6b64c88fbb8df7fced46a01e41b7feb12785e4181498669b languageName: node linkType: hard @@ -2527,22 +2547,22 @@ __metadata: resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/i18n": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@kusto/monaco-kusto": "npm:^10.0.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2574,19 +2594,19 @@ __metadata: resolution: "@grafana-plugins/grafana-postgresql-datasource@workspace:public/app/plugins/datasource/grafana-postgresql-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/sql": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/sql": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2606,18 +2626,18 @@ __metadata: resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2646,20 +2666,20 @@ __metadata: resolution: "@grafana-plugins/grafana-testdata-datasource@workspace:public/app/plugins/datasource/grafana-testdata-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2696,12 +2716,12 @@ __metadata: "@grafana/ui": "workspace:*" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-window": "npm:1.8.8" @@ -2730,23 +2750,23 @@ __metadata: resolution: "@grafana-plugins/loki@workspace:public/app/plugins/datasource/loki" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/lezer-logql": "npm:0.2.8" "@grafana/llm": "npm:0.22.1" "@grafana/monaco-logql": "npm:^0.0.8" - "@grafana/plugin-configs": "npm:12.1.0-pre" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2773,20 +2793,20 @@ __metadata: resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/i18n": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/sql": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/sql": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" i18next-parser: "npm:9.3.0" lodash: "npm:4.17.21" @@ -2806,19 +2826,19 @@ __metadata: resolution: "@grafana-plugins/mysql@workspace:public/app/plugins/datasource/mysql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/sql": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/sql": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2838,16 +2858,16 @@ __metadata: resolution: "@grafana-plugins/parca@workspace:public/app/plugins/datasource/parca" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/plugin-configs": "npm:12.1.0-pre" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -2871,22 +2891,22 @@ __metadata: resolution: "@grafana-plugins/stackdriver@workspace:public/app/plugins/datasource/cloud-monitoring" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/google-sdk": "npm:0.3.4" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2920,10 +2940,10 @@ __metadata: "@emotion/css": "npm:11.13.5" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/lezer-traceql": "npm:0.0.22" + "@grafana/lezer-traceql": "npm:0.0.23" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" @@ -2935,11 +2955,11 @@ __metadata: "@opentelemetry/semantic-conventions": "npm:1.36.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2987,10 +3007,10 @@ __metadata: "@grafana/ui": "workspace:*" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -3030,7 +3050,7 @@ __metadata: react-redux: "npm:^9.2.0" rimraf: "npm:^6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" type-fest: "npm:^4.40.0" typescript: "npm:5.8.3" @@ -3044,6 +3064,20 @@ __metadata: languageName: unknown linkType: soft +"@grafana/assistant@npm:0.0.10": + version: 0.0.10 + resolution: "@grafana/assistant@npm:0.0.10" + peerDependencies: + "@grafana/data": ">=12.1.0" + "@grafana/runtime": ">=12.1.0" + "@grafana/scenes": ">=5.41.0" + "@grafana/ui": ">=12.1.0" + react: ">=18.0.0" + rxjs: ">=7.0.0" + checksum: 10/4b5f0ea03e8cebba07285d4f2ea642211acde0e70207e11456640169fd32d6c550fd3be9256ca41266438237d129b28446981efaa2399e20c2cce064080c1e04 + languageName: node + linkType: hard + "@grafana/async-query-data@npm:0.4.1": version: 0.4.1 resolution: "@grafana/async-query-data@npm:0.4.1" @@ -3074,19 +3108,20 @@ __metadata: languageName: node linkType: hard -"@grafana/data@npm:12.1.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@npm:12.2.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": version: 0.0.0-use.local resolution: "@grafana/data@workspace:packages/grafana-data" dependencies: "@braintree/sanitize-url": "npm:7.0.1" - "@grafana/schema": "npm:12.1.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@leeoniya/ufuzzy": "npm:1.0.18" "@rollup/plugin-node-resolve": "npm:16.0.1" "@types/d3-interpolate": "npm:^3.0.0" "@types/history": "npm:4.7.11" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/papaparse": "npm:5.3.16" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -3096,23 +3131,23 @@ __metadata: d3-interpolate: "npm:3.0.1" date-fns: "npm:4.1.0" dompurify: "npm:3.2.6" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" eventemitter3: "npm:5.0.1" fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" lodash: "npm:4.17.21" - marked: "npm:16.0.0" + marked: "npm:16.1.1" marked-mangle: "npm:1.1.11" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" - ol: "npm:10.6.0" + ol: "npm:10.6.1" papaparse: "npm:5.5.3" react: "npm:18.3.1" react-dom: "npm:18.3.1" react-use: "npm:17.6.0" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" rxjs: "npm:7.8.2" string-hash: "npm:^1.1.3" @@ -3127,18 +3162,18 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@npm:12.1.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@npm:12.2.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:16.0.1" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/semver": "npm:7.7.0" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" semver: "npm:^7.7.0" tslib: "npm:2.8.1" @@ -3175,7 +3210,7 @@ __metadata: dependencies: "@typescript-eslint/types": "npm:^8.9.0" "@typescript-eslint/utils": "npm:^8.9.0" - eslint: "npm:9.19.0" + eslint: "npm:9.32.0" tslib: "npm:2.8.1" languageName: unknown linkType: soft @@ -3224,29 +3259,29 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/flamegraph@workspace:packages/grafana-flamegraph" dependencies: - "@babel/core": "npm:7.26.10" - "@babel/preset-env": "npm:7.26.9" - "@babel/preset-react": "npm:7.26.3" + "@babel/core": "npm:7.28.0" + "@babel/preset-env": "npm:7.28.0" + "@babel/preset-react": "npm:7.27.1" "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@leeoniya/ufuzzy": "npm:1.0.18" "@rollup/plugin-node-resolve": "npm:16.0.1" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/d3": "npm:^7" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-virtualized-auto-sizer": "npm:1.0.8" "@types/tinycolor2": "npm:1.4.6" babel-jest: "npm:29.7.0" d3: "npm:^7.8.5" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" jest: "npm:^29.6.4" jest-canvas-mock: "npm:2.5.2" lodash: "npm:4.17.21" @@ -3254,7 +3289,7 @@ __metadata: react-use: "npm:17.6.0" react-virtualized-auto-sizer: "npm:1.0.26" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" tinycolor2: "npm:1.6.0" ts-jest: "npm:29.2.5" @@ -3279,7 +3314,7 @@ __metadata: languageName: node linkType: hard -"@grafana/i18n@npm:12.1.0-pre, @grafana/i18n@workspace:*, @grafana/i18n@workspace:packages/grafana-i18n": +"@grafana/i18n@npm:12.2.0-pre, @grafana/i18n@workspace:*, @grafana/i18n@workspace:packages/grafana-i18n": version: 0.0.0-use.local resolution: "@grafana/i18n@workspace:packages/grafana-i18n" dependencies: @@ -3310,12 +3345,12 @@ __metadata: languageName: node linkType: hard -"@grafana/lezer-traceql@npm:0.0.22": - version: 0.0.22 - resolution: "@grafana/lezer-traceql@npm:0.0.22" +"@grafana/lezer-traceql@npm:0.0.23": + version: 0.0.23 + resolution: "@grafana/lezer-traceql@npm:0.0.23" peerDependencies: "@lezer/lr": ^1.4.2 - checksum: 10/e7f640e902d0738c72950a7584b5ceed68114582eb9c33546bf3fbc938baf7a460b93cd9b90e65a0d8f16a3870ecc282c6ecc12a6c321232247abc05111662df + checksum: 10/e9dba8ed747062c791fd2298e20e18da06642d273f9c502f8c1ec90524108edf7c6bd4e719db2ae62de77ba02bfaf0d2f291058d04727969b5af67cdb277b9c3 languageName: node linkType: hard @@ -3350,19 +3385,19 @@ __metadata: resolution: "@grafana/o11y-ds-frontend@workspace:packages/grafana-o11y-ds-frontend" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/systemjs": "npm:6.15.3" jest: "npm:^29.6.4" @@ -3380,7 +3415,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-configs@npm:12.1.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": +"@grafana/plugin-configs@npm:12.2.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": version: 0.0.0-use.local resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs" dependencies: @@ -3391,9 +3426,9 @@ __metadata: "@types/eslint": "npm:9.6.1" "@types/webpack-bundle-analyzer": "npm:^4.7.0" copy-webpack-plugin: "npm:12.0.2" - eslint: "npm:9.19.0" + eslint: "npm:9.32.0" eslint-webpack-plugin: "npm:4.2.0" - fork-ts-checker-webpack-plugin: "npm:9.0.2" + fork-ts-checker-webpack-plugin: "npm:9.1.0" glob: "npm:11.0.3" identity-obj-proxy: "npm:^3.0.0" imports-loader: "npm:^5.0.0" @@ -3409,17 +3444,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-e2e@npm:2.0.0": - version: 2.0.0 - resolution: "@grafana/plugin-e2e@npm:2.0.0" +"@grafana/plugin-e2e@npm:2.1.7": + version: 2.1.7 + resolution: "@grafana/plugin-e2e@npm:2.1.7" dependencies: - "@grafana/e2e-selectors": "npm:^12.1.0-240060" + "@grafana/e2e-selectors": "npm:^12.1.0-254610" semver: "npm:^7.5.4" uuid: "npm:^11.0.2" yaml: "npm:^2.3.4" peerDependencies: "@playwright/test": ^1.52.0 - checksum: 10/d9bdc1f072e97465904949cc16fce9017c443173aebcf142d77a409bea743fcded80736e55a0a19e95f890cfd9b3d8c68327e0dbcd1799a32e63c168771af5d9 + checksum: 10/4e14bda1c6304c89a1ffca972dda18d3ab1fad59ee30e6735112d75f7c5c40856091e2e17f46451aec5261b6368f67c245edb112072d98c9067723f4ca2c71c3 languageName: node linkType: hard @@ -3456,15 +3491,15 @@ __metadata: resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus" dependencies: "@emotion/css": "npm:11.13.5" - "@floating-ui/react": "npm:0.27.13" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/i18n": "npm:12.1.0-pre" + "@floating-ui/react": "npm:0.27.14" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@hello-pangea/dnd": "npm:18.0.1" "@leeoniya/ufuzzy": "npm:1.0.18" "@lezer/common": "npm:1.2.3" @@ -3477,12 +3512,12 @@ __metadata: "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:16.0.1" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/pluralize": "npm:^0.0.33" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" @@ -3492,14 +3527,14 @@ __metadata: "@types/semver": "npm:7.7.0" "@types/uuid": "npm:10.0.0" debounce-promise: "npm:3.1.2" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" i18next-parser: "npm:9.3.0" jest: "npm:29.7.0" jest-environment-jsdom: "npm:29.7.0" lodash: "npm:4.17.21" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" - monaco-promql: "npm:1.7.4" + monaco-promql: "npm:1.8.0" pluralize: "npm:8.0.0" prismjs: "npm:1.30.0" react: "npm:18.3.1" @@ -3510,7 +3545,7 @@ __metadata: react-window: "npm:1.8.11" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" rxjs: "npm:7.8.2" semver: "npm:7.7.2" @@ -3523,20 +3558,20 @@ __metadata: languageName: unknown linkType: soft -"@grafana/runtime@npm:12.1.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": +"@grafana/runtime@npm:12.2.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": version: 0.0.0-use.local resolution: "@grafana/runtime@workspace:packages/grafana-runtime" dependencies: - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/schema": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@rollup/plugin-node-resolve": "npm:16.0.1" "@rollup/plugin-terser": "npm:0.4.4" "@testing-library/dom": "npm:10.4.0" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/history": "npm:4.7.11" "@types/jest": "npm:29.5.14" @@ -3544,7 +3579,7 @@ __metadata: "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/systemjs": "npm:6.15.3" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" history: "npm:4.10.1" lodash: "npm:4.17.21" react: "npm:18.3.1" @@ -3553,7 +3588,7 @@ __metadata: react-use: "npm:17.6.0" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" rollup-plugin-sourcemaps: "npm:0.6.3" rxjs: "npm:7.8.2" @@ -3566,10 +3601,10 @@ __metadata: linkType: soft "@grafana/scenes-react@npm:^6.27.2": - version: 6.27.2 - resolution: "@grafana/scenes-react@npm:6.27.2" + version: 6.28.1 + resolution: "@grafana/scenes-react@npm:6.28.1" dependencies: - "@grafana/scenes": "npm:6.27.2" + "@grafana/scenes": "npm:6.28.1" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3581,13 +3616,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/415186350304d999a13fa1e66f8bb7675cb28d5174a9449b8cb3d6a25efaecfc74a0e2e69cde9513f6fd9cac9bd2891c12e93a974ec15cfa0d6d04e5b95b5306 + checksum: 10/03278682a8ff7ebb399d522b3f54aee820cb34ab85f79d321ee44c4fbb1a490b6d62a8f03cae68187a70cc4345715d41441a72ae3915e6bebfe20fbfb61f9108 languageName: node linkType: hard -"@grafana/scenes@npm:6.27.2, @grafana/scenes@npm:^6.27.2": - version: 6.27.2 - resolution: "@grafana/scenes@npm:6.27.2" +"@grafana/scenes@npm:6.28.1, @grafana/scenes@npm:^6.27.2": + version: 6.28.1 + resolution: "@grafana/scenes@npm:6.28.1" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3607,47 +3642,47 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/8ba3d6c9b29de32a943a4a53511822f27eb51b35270368f0b9d80208eab50ae8767c6b372a7c7779a213618eab24bbb3e441dde42b038fc0a2078989879e0d14 + checksum: 10/603cb2b421e59a51ee36af6f1d6554e6f328db3aea42da7960e33c14de4ce298b2c261affa6dd651fe8d4ffb4a58e7825958a3b66d00daa3410e91f5f1fb185f languageName: node linkType: hard -"@grafana/schema@npm:12.1.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@npm:12.2.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:16.0.1" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" glob: "npm:^11.0.0" rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" tslib: "npm:2.8.1" typescript: "npm:5.8.3" languageName: unknown linkType: soft -"@grafana/sql@npm:12.1.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": +"@grafana/sql@npm:12.2.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": version: 0.0.0-use.local resolution: "@grafana/sql@workspace:packages/grafana-sql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" - "@grafana/i18n": "npm:12.1.0-pre" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" "@grafana/plugin-ui": "npm:0.10.7" - "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.2.0-pre" "@react-awesome-query-builder/ui": "npm:6.6.15" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-virtualized-auto-sizer": "npm:1.0.8" @@ -3679,7 +3714,7 @@ __metadata: resolution: "@grafana/test-utils@workspace:packages/grafana-test-utils" dependencies: "@grafana/tsconfig": "npm:^2.0.0" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" msw: "npm:2.10.4" typescript: "npm:5.8.3" languageName: unknown @@ -3692,21 +3727,21 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@npm:12.1.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@npm:12.2.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: - "@babel/core": "npm:7.26.10" + "@babel/core": "npm:7.28.0" "@emotion/css": "npm:11.13.5" "@emotion/react": "npm:11.14.0" "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" - "@floating-ui/react": "npm:0.27.13" - "@grafana/data": "npm:12.1.0-pre" - "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@floating-ui/react": "npm:0.27.14" + "@grafana/data": "npm:12.2.0-pre" + "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/i18n": "npm:12.1.0-pre" - "@grafana/schema": "npm:12.1.0-pre" + "@grafana/i18n": "npm:12.2.0-pre" + "@grafana/schema": "npm:12.2.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@hello-pangea/dnd": "npm:18.0.1" "@monaco-editor/react": "npm:4.7.0" @@ -3731,22 +3766,23 @@ __metadata: "@storybook/preview-api": "npm:^8.6.2" "@storybook/react": "npm:^8.6.2" "@storybook/react-webpack5": "npm:^8.6.2" + "@storybook/test-runner": "npm:^0.23.0" "@storybook/theming": "npm:^8.6.2" "@tanstack/react-virtual": "npm:^3.5.1" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/chance": "npm:^1.1.7" "@types/common-tags": "npm:^1.8.0" "@types/d3": "npm:7.4.3" - "@types/hoist-non-react-statics": "npm:3.3.6" + "@types/hoist-non-react-statics": "npm:3.3.7" "@types/is-hotkey": "npm:0.1.10" "@types/jest": "npm:29.5.14" "@types/jquery": "npm:3.5.32" "@types/lodash": "npm:4.17.20" "@types/mock-raf": "npm:1.0.6" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-color": "npm:3.0.13" @@ -3765,13 +3801,13 @@ __metadata: classnames: "npm:2.5.1" clsx: "npm:^2.1.1" common-tags: "npm:1.8.2" - core-js: "npm:3.40.0" + core-js: "npm:3.44.0" css-loader: "npm:7.1.2" csstype: "npm:3.1.3" d3: "npm:7.9.0" date-fns: "npm:4.1.0" downshift: "npm:^9.0.6" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" expose-loader: "npm:5.0.1" fs-extra: "npm:^11.2.0" hoist-non-react-statics: "npm:3.3.2" @@ -3787,7 +3823,7 @@ __metadata: monaco-editor: "npm:0.34.1" msw: "npm:^2.10.2" msw-storybook-addon: "npm:^2.0.5" - ol: "npm:10.6.0" + ol: "npm:10.6.1" prismjs: "npm:1.30.0" process: "npm:^0.11.10" rc-cascader: "npm:3.34.0" @@ -3818,11 +3854,11 @@ __metadata: rimraf: "npm:6.0.1" rollup: "npm:^4.22.4" rollup-plugin-copy: "npm:3.5.0" - rollup-plugin-esbuild: "npm:6.2.0" + rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" rollup-plugin-svg-import: "npm:3.0.0" rxjs: "npm:7.8.2" - sass-loader: "npm:16.0.4" + sass-loader: "npm:16.0.5" slate: "npm:0.47.9" slate-plain-serializer: "npm:0.7.13" slate-react: "npm:0.22.10" @@ -3850,6 +3886,22 @@ __metadata: languageName: node linkType: hard +"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": + version: 9.3.0 + resolution: "@hapi/hoek@npm:9.3.0" + checksum: 10/ad83a223787749f3873bce42bd32a9a19673765bf3edece0a427e138859ff729469e68d5fdf9ff6bbee6fb0c8e21bab61415afa4584f527cfc40b59ea1957e70 + languageName: node + linkType: hard + +"@hapi/topo@npm:^5.1.0": + version: 5.1.0 + resolution: "@hapi/topo@npm:5.1.0" + dependencies: + "@hapi/hoek": "npm:^9.0.0" + checksum: 10/084bfa647015f4fd3fdd51fadb2747d09ef2f5e1443d6cbada2988b0c88494f85edf257ec606c790db146ac4e34ff57f3fcb22e3299b8e06ed5c87ba7583495c + languageName: node + linkType: hard + "@hello-pangea/dnd@npm:18.0.1": version: 18.0.1 resolution: "@hello-pangea/dnd@npm:18.0.1" @@ -3915,10 +3967,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/retry@npm:^0.4.1": - version: 0.4.1 - resolution: "@humanwhocodes/retry@npm:0.4.1" - checksum: 10/39fafc7319e88f61befebd5e1b4f0136534ea6a9bd10d74366698187bd63544210ec5d79a87ed4d91297f1cc64c4c53d45fb0077a2abfdce212cf0d3862d5f04 +"@humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10/0b32cfd362bea7a30fbf80bb38dcaf77fee9c2cae477ee80b460871d03590110ac9c77d654f04ec5beaf71b6f6a89851bdf6c1e34ccdf2f686bd86fcd97d9e61 languageName: node linkType: hard @@ -4680,14 +4732,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.12 + resolution: "@jridgewell/gen-mapping@npm:0.3.12" dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/81587b3c4dd8e6c60252122937cea0c637486311f4ed208b52b62aae2e7a87598f63ec330e6cd0984af494bfb16d3f0d60d3b21d7e5b4aedd2602ff3fe9d32e2 + checksum: 10/151667531566417a940d4dd0a319724979f7a90b9deb9f1617344e1183887d78c835bc1a9209c1ee10fc8a669cdd7ac8120a43a2b6bc8d0d5dd18a173059ff4b languageName: node linkType: hard @@ -4698,7 +4749,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.2.1": +"@jridgewell/set-array@npm:^1.0.0": version: 1.2.1 resolution: "@jridgewell/set-array@npm:1.2.1" checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 @@ -4732,13 +4783,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/dced32160a44b49d531b80a4a2159dceab6b3ddf0c8e95a0deae4b0e894b172defa63d5ac52a19c2068e1fe7d31ea4ba931fbeec103233ecb4208953967120fc + checksum: 10/64e1ce0dc3a9e56b0118eaf1b2f50746fd59a36de37516cc6855b5370d5f367aa8229e1237536d738262e252c70ee229619cb04e3f3b822146ee3eb1b7ab297f languageName: node linkType: hard @@ -4812,13 +4863,6 @@ __metadata: languageName: node linkType: hard -"@leichtgewicht/ip-codec@npm:^2.0.1": - version: 2.0.3 - resolution: "@leichtgewicht/ip-codec@npm:2.0.3" - checksum: 10/1144b3634f02532316d811d33844c992caf7dc70e1dde454952fb446a3c67848b3e76deb3e8de2a7a29faaf7cee34bd00890b647508cd8d44623b1a2953ed030 - languageName: node - linkType: hard - "@lerna/create@npm:8.2.3": version: 8.2.3 resolution: "@lerna/create@npm:8.2.3" @@ -6135,37 +6179,36 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.52.0": - version: 1.52.0 - resolution: "@playwright/test@npm:1.52.0" +"@playwright/test@npm:1.54.1": + version: 1.54.1 + resolution: "@playwright/test@npm:1.54.1" dependencies: - playwright: "npm:1.52.0" + playwright: "npm:1.54.1" bin: playwright: cli.js - checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88 + checksum: 10/75ea34818f57f11bd8a1e6e3ac202c752c520c98467607be67c0eccfe41ea8ff6ae6824e91cb5ecacbee403aef4e1e6210500ee3cc82dd9317a8ee7136af18d3 languageName: node linkType: hard -"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15": - version: 0.5.15 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15" +"@pmmmwh/react-refresh-webpack-plugin@npm:0.6.1": + version: 0.6.1 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.6.1" dependencies: - ansi-html: "npm:^0.0.9" + anser: "npm:^2.1.1" core-js-pure: "npm:^3.23.3" error-stack-parser: "npm:^2.0.6" html-entities: "npm:^2.1.0" - loader-utils: "npm:^2.0.4" schema-utils: "npm:^4.2.0" source-map: "npm:^0.7.3" peerDependencies: - "@types/webpack": 4.x || 5.x + "@types/webpack": 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 type-fest: ">=0.17.0 <5.0.0" - webpack: ">=4.43.0 <6.0.0" - webpack-dev-server: 3.x || 4.x || 5.x + webpack: ^5.0.0 + webpack-dev-server: ^4.8.0 || 5.x webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x + webpack-plugin-serve: 1.x peerDependenciesMeta: "@types/webpack": optional: true @@ -6179,7 +6222,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 10/d8c978654c4c6873edc3336bca87d359d3a7f32571e8404af8a3defd0e515aa34d9dc8324a9157d0220d72fb8a6a350660301c2757df964f845422a898714bc7 + checksum: 10/b72c50c26aacb7775929fb00871c180c5a6a7fd0450e20df6dd4a2407f770f6d7795e9eb214a026c3d9c1e692c0ddbfaf056711f76b1529f4ad578f637e2fb1f languageName: node linkType: hard @@ -7159,6 +7202,29 @@ __metadata: languageName: node linkType: hard +"@sideway/address@npm:^4.1.5": + version: 4.1.5 + resolution: "@sideway/address@npm:4.1.5" + dependencies: + "@hapi/hoek": "npm:^9.0.0" + checksum: 10/c4c73ac0339504f34e016d3a687118e7ddf197c1c968579572123b67b230be84caa705f0f634efdfdde7f2e07a6e0224b3c70665dc420d8bc95bf400cfc4c998 + languageName: node + linkType: hard + +"@sideway/formula@npm:^3.0.1": + version: 3.0.1 + resolution: "@sideway/formula@npm:3.0.1" + checksum: 10/8d3ee7f80df4e5204b2cbe92a2a711ca89684965a5c9eb3b316b7051212d3522e332a65a0bb2a07cc708fcd1d0b27fcb30f43ff0bcd5089d7006c7160a89eefe + languageName: node + linkType: hard + +"@sideway/pinpoint@npm:^2.0.0": + version: 2.0.0 + resolution: "@sideway/pinpoint@npm:2.0.0" + checksum: 10/1ed21800128b2b23280ba4c9db26c8ff6142b97a8683f17639fd7f2128aa09046461574800b30fb407afc5b663c2331795ccf3b654d4b38fa096e41a5c786bf8 + languageName: node + linkType: hard + "@sigstore/bundle@npm:^2.3.2": version: 2.3.2 resolution: "@sigstore/bundle@npm:2.3.2" @@ -7750,6 +7816,36 @@ __metadata: languageName: node linkType: hard +"@storybook/test-runner@npm:^0.23.0": + version: 0.23.0 + resolution: "@storybook/test-runner@npm:0.23.0" + dependencies: + "@babel/core": "npm:^7.22.5" + "@babel/generator": "npm:^7.22.5" + "@babel/template": "npm:^7.22.5" + "@babel/types": "npm:^7.22.5" + "@jest/types": "npm:^29.6.3" + "@swc/core": "npm:^1.5.22" + "@swc/jest": "npm:^0.2.23" + expect-playwright: "npm:^0.8.0" + jest: "npm:^29.6.4" + jest-circus: "npm:^29.6.4" + jest-environment-node: "npm:^29.6.4" + jest-junit: "npm:^16.0.0" + jest-playwright-preset: "npm:^4.0.0" + jest-runner: "npm:^29.6.4" + jest-serializer-html: "npm:^7.1.0" + jest-watch-typeahead: "npm:^2.0.0" + nyc: "npm:^15.1.0" + playwright: "npm:^1.14.0" + peerDependencies: + storybook: ^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 + bin: + test-storybook: dist/test-storybook.js + checksum: 10/42cb993ea3647cbe71495ea095c71d28970b465796dd6b85c15bf41d7fbc3d0f44b0eceb55b200d34d70d2e05e681a759d7164c46f911a43a338ec918d188e25 + languageName: node + linkType: hard + "@storybook/test@npm:8.6.2": version: 8.6.2 resolution: "@storybook/test@npm:8.6.2" @@ -8355,9 +8451,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-darwin-arm64@npm:1.11.4" +"@swc/core-darwin-arm64@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-darwin-arm64@npm:1.13.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -8369,9 +8465,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-darwin-x64@npm:1.11.4" +"@swc/core-darwin-x64@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-darwin-x64@npm:1.13.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -8383,9 +8479,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.4" +"@swc/core-linux-arm-gnueabihf@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.13.2" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -8397,9 +8493,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.4" +"@swc/core-linux-arm64-gnu@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-linux-arm64-gnu@npm:1.13.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -8411,9 +8507,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.4" +"@swc/core-linux-arm64-musl@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-linux-arm64-musl@npm:1.13.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -8425,9 +8521,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.4" +"@swc/core-linux-x64-gnu@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-linux-x64-gnu@npm:1.13.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -8439,9 +8535,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-linux-x64-musl@npm:1.11.4" +"@swc/core-linux-x64-musl@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-linux-x64-musl@npm:1.13.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -8453,9 +8549,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.4" +"@swc/core-win32-arm64-msvc@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-win32-arm64-msvc@npm:1.13.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -8467,9 +8563,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.4" +"@swc/core-win32-ia32-msvc@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-win32-ia32-msvc@npm:1.13.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -8481,9 +8577,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.4": - version: 1.11.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.4" +"@swc/core-win32-x64-msvc@npm:1.13.2": + version: 1.13.2 + resolution: "@swc/core-win32-x64-msvc@npm:1.13.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -8534,24 +8630,24 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:^1.10.8": - version: 1.11.4 - resolution: "@swc/core@npm:1.11.4" +"@swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": + version: 1.13.2 + resolution: "@swc/core@npm:1.13.2" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.4" - "@swc/core-darwin-x64": "npm:1.11.4" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.4" - "@swc/core-linux-arm64-gnu": "npm:1.11.4" - "@swc/core-linux-arm64-musl": "npm:1.11.4" - "@swc/core-linux-x64-gnu": "npm:1.11.4" - "@swc/core-linux-x64-musl": "npm:1.11.4" - "@swc/core-win32-arm64-msvc": "npm:1.11.4" - "@swc/core-win32-ia32-msvc": "npm:1.11.4" - "@swc/core-win32-x64-msvc": "npm:1.11.4" + "@swc/core-darwin-arm64": "npm:1.13.2" + "@swc/core-darwin-x64": "npm:1.13.2" + "@swc/core-linux-arm-gnueabihf": "npm:1.13.2" + "@swc/core-linux-arm64-gnu": "npm:1.13.2" + "@swc/core-linux-arm64-musl": "npm:1.13.2" + "@swc/core-linux-x64-gnu": "npm:1.13.2" + "@swc/core-linux-x64-musl": "npm:1.13.2" + "@swc/core-win32-arm64-msvc": "npm:1.13.2" + "@swc/core-win32-ia32-msvc": "npm:1.13.2" + "@swc/core-win32-x64-msvc": "npm:1.13.2" "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.19" + "@swc/types": "npm:^0.1.23" peerDependencies: - "@swc/helpers": "*" + "@swc/helpers": ">=0.5.17" dependenciesMeta: "@swc/core-darwin-arm64": optional: true @@ -8576,7 +8672,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/9dce8808f8c17be58c0e42f14d1c257db0418f7f426774e2f05794eb4b9011445c7ac13805e7ae0dad53b858720eabaffe0bcc5366696007e166bc4ab50fbe4d + checksum: 10/ca4d05c44c3e36f7d3f68dda9b53eb1e7e1c9f07701d526c8484605bf4a744cbcb83e1cadb0b4b57723d98ba799d3a75508b2219751ad5fdb29491ca325c680d languageName: node linkType: hard @@ -8596,7 +8692,7 @@ __metadata: languageName: node linkType: hard -"@swc/jest@npm:^0.2.26": +"@swc/jest@npm:^0.2.23, @swc/jest@npm:^0.2.26": version: 0.2.39 resolution: "@swc/jest@npm:0.2.39" dependencies: @@ -8609,12 +8705,12 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.17, @swc/types@npm:^0.1.19": - version: 0.1.19 - resolution: "@swc/types@npm:0.1.19" +"@swc/types@npm:^0.1.17, @swc/types@npm:^0.1.23": + version: 0.1.23 + resolution: "@swc/types@npm:0.1.23" dependencies: "@swc/counter": "npm:^0.1.3" - checksum: 10/693147cc9b23147164ddff9cb89477c369fbeb103319584779352a9ff1c72e0a70b97a89dfd97629040db8956d668d7b7a8fed328ffea46a3e8c18577e396994 + checksum: 10/8d9d73dd1fc9335105105da57595ab913bad6addd4fbcb2eb147300694630232225eb7dc74b733205af33352803e4fcefc18e3a36f8924cf821ef91384767670 languageName: node linkType: hard @@ -8747,27 +8843,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/react@npm:16.2.0": - version: 16.2.0 - resolution: "@testing-library/react@npm:16.2.0" - dependencies: - "@babel/runtime": "npm:^7.12.5" - peerDependencies: - "@testing-library/dom": ^10.0.0 - "@types/react": ^18.0.0 || ^19.0.0 - "@types/react-dom": ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/cf10bfa9a363384e6861417696fff4a464a64f98ec6f0bb7f1fa7cbb550d075d23a2f6a943b7df85dded7bde3234f6ea6b6e36f95211f4544b846ea72c288289 - languageName: node - linkType: hard - -"@testing-library/react@npm:^16.3.0": +"@testing-library/react@npm:16.3.0, @testing-library/react@npm:^16.3.0": version: 16.3.0 resolution: "@testing-library/react@npm:16.3.0" dependencies: @@ -8933,9 +9009,9 @@ __metadata: linkType: hard "@types/babel__preset-env@npm:^7": - version: 7.9.7 - resolution: "@types/babel__preset-env@npm:7.9.7" - checksum: 10/624425a84d9149aec04795fed6b1ac2f27dfd5d7976fde479bb1a4d754de34c92cdc28a1a373a5826382a68127b536420a0e090aa5fae522cb62724b7a571cb5 + version: 7.10.0 + resolution: "@types/babel__preset-env@npm:7.10.0" + checksum: 10/7d4d12758d89708afe327079d7d7580e8af3292295f087b8a9a48e12ac1d90aadc18ac3bc00f9b0cbc8778f3ce9fe778801d4d49b7691a75e3f13a901b69fd07 languageName: node linkType: hard @@ -8958,25 +9034,6 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*": - version: 1.19.1 - resolution: "@types/body-parser@npm:1.19.1" - dependencies: - "@types/connect": "npm:*" - "@types/node": "npm:*" - checksum: 10/2990656ea2de81f3529a3359a79a13b67feb4c627caf7a367fdc0017a178e567b0cc410546bdd219104ad7197c5ee5a90b70193f5253839ea43d9cdb2d2dacee - languageName: node - linkType: hard - -"@types/bonjour@npm:^3.5.13": - version: 3.5.13 - resolution: "@types/bonjour@npm:3.5.13" - dependencies: - "@types/node": "npm:*" - checksum: 10/e827570e097bd7d625a673c9c208af2d1a22fa3885c0a1646533cf24394c839c3e5f60ac1bc60c0ddcc69c0615078c9fb2c01b42596c7c582d895d974f2409ee - languageName: node - linkType: hard - "@types/chance@npm:^1.1.7": version: 1.1.7 resolution: "@types/chance@npm:1.1.7" @@ -8998,17 +9055,7 @@ __metadata: languageName: node linkType: hard -"@types/connect-history-api-fallback@npm:^1.5.4": - version: 1.5.4 - resolution: "@types/connect-history-api-fallback@npm:1.5.4" - dependencies: - "@types/express-serve-static-core": "npm:*" - "@types/node": "npm:*" - checksum: 10/e1dee43b8570ffac02d2d47a2b4ba80d3ca0dd1840632dafb221da199e59dbe3778d3d7303c9e23c6b401f37c076935a5bc2aeae1c4e5feaefe1c371fe2073fd - languageName: node - linkType: hard - -"@types/connect@npm:*, @types/connect@npm:3.4.38": +"@types/connect@npm:3.4.38": version: 3.4.38 resolution: "@types/connect@npm:3.4.38" dependencies: @@ -9409,30 +9456,6 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33": - version: 4.19.6 - resolution: "@types/express-serve-static-core@npm:4.19.6" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10/a2e00b6c5993f0dd63ada2239be81076fe0220314b9e9fde586e8946c9c09ce60f9a2dd0d74410ee2b5fd10af8c3e755a32bb3abf134533e2158142488995455 - languageName: node - linkType: hard - -"@types/express@npm:*, @types/express@npm:^4.17.21": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^4.17.33" - "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 - languageName: node - linkType: hard - "@types/file-saver@npm:2.0.7": version: 2.0.7 resolution: "@types/file-saver@npm:2.0.7" @@ -9532,13 +9555,14 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:3.3.6, @types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.6 - resolution: "@types/hoist-non-react-statics@npm:3.3.6" +"@types/hoist-non-react-statics@npm:3.3.7, @types/hoist-non-react-statics@npm:^3.3.1": + version: 3.3.7 + resolution: "@types/hoist-non-react-statics@npm:3.3.7" dependencies: - "@types/react": "npm:*" hoist-non-react-statics: "npm:^3.3.0" - checksum: 10/f03e43bd081876c49584ffa0eb690d69991f258203efca44dcc30efdda49a50653ff06402917d1edc9cb7e2adebbe9e2d1d0e739bc99c1b5372103b1cc534e47 + peerDependencies: + "@types/react": "*" + checksum: 10/13f610572c073970b3f43cc446396974fed786fee6eac2d6fd4b0ca5c985f13e79d4a0de58af4e5b4c68470d808567c3a14108d98edb7d526d4d46c8ec851ed1 languageName: node linkType: hard @@ -9549,22 +9573,6 @@ __metadata: languageName: node linkType: hard -"@types/http-errors@npm:*": - version: 2.0.4 - resolution: "@types/http-errors@npm:2.0.4" - checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 - languageName: node - linkType: hard - -"@types/http-proxy@npm:^1.17.8": - version: 1.17.8 - resolution: "@types/http-proxy@npm:1.17.8" - dependencies: - "@types/node": "npm:*" - checksum: 10/426c910286b7d83d67199ef3d03e9b7e9f9a33e76b362fe4a7008e7d40bb638df7998a2e3b801aaa635a9ba321a161a5f200687a29814b8c53eaa42902d30e45 - languageName: node - linkType: hard - "@types/ini@npm:^4": version: 4.1.1 resolution: "@types/ini@npm:4.1.1" @@ -9589,14 +9597,7 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": - version: 2.0.3 - resolution: "@types/istanbul-lib-coverage@npm:2.0.3" - checksum: 10/0650cba4be8f464bee89b9de0b71a5ea3b5cc676ce24e1196b5d6a51542ce9e613ae4549bf19756bb33dbbbb32b47931040266100062bfb197c597d73e341eb0 - languageName: node - linkType: hard - -"@types/istanbul-lib-coverage@npm:^2.0.6": +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" checksum: 10/3feac423fd3e5449485afac999dcfcb3d44a37c830af898b689fadc65d26526460bedb889db278e0d4d815a670331796494d073a10ee6e3a6526301fe7415778 @@ -9612,16 +9613,7 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-reports@npm:^3.0.0": - version: 3.0.1 - resolution: "@types/istanbul-reports@npm:3.0.1" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10/f1ad54bc68f37f60b30c7915886b92f86b847033e597f9b34f2415acdbe5ed742fa559a0a40050d74cdba3b6a63c342cac1f3a64dba5b68b66a6941f4abd7903 - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.4": +"@types/istanbul-reports@npm:^3.0.0, @types/istanbul-reports@npm:^3.0.4": version: 3.0.4 resolution: "@types/istanbul-reports@npm:3.0.4" dependencies: @@ -9770,20 +9762,6 @@ __metadata: languageName: node linkType: hard -"@types/mime@npm:*": - version: 3.0.4 - resolution: "@types/mime@npm:3.0.4" - checksum: 10/a6139c8e1f705ef2b064d072f6edc01f3c099023ad7c4fce2afc6c2bf0231888202adadbdb48643e8e20da0ce409481a49922e737eca52871b3dc08017455843 - languageName: node - linkType: hard - -"@types/mime@npm:^1": - version: 1.3.5 - resolution: "@types/mime@npm:1.3.5" - checksum: 10/e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 - languageName: node - linkType: hard - "@types/minimatch@npm:*, @types/minimatch@npm:^5.1.2": version: 5.1.2 resolution: "@types/minimatch@npm:5.1.2" @@ -9812,21 +9790,21 @@ __metadata: languageName: node linkType: hard -"@types/node-forge@npm:^1, @types/node-forge@npm:^1.3.0": - version: 1.3.12 - resolution: "@types/node-forge@npm:1.3.12" +"@types/node-forge@npm:^1": + version: 1.3.13 + resolution: "@types/node-forge@npm:1.3.13" dependencies: "@types/node": "npm:*" - checksum: 10/6840622b4253e04f1848fda7355603d75d0553d9e103e8eeb3c2cd832fe1f6af8a27ebfd70ff8a6b2a6c3737054ca280a9f9b7fb9cfbfb6be69c45ca4861c4aa + checksum: 10/4d62a6b0cedeb45145de6b05df0082b0ba32675aeb1bf8dbe003804eb61be412a613e82f56b65ba1051594abda4f4c9c0aa9aac009cf106af6faf6217eee8681 languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:22.15.0, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": - version: 22.15.0 - resolution: "@types/node@npm:22.15.0" +"@types/node@npm:*, @types/node@npm:22.16.5, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": + version: 22.16.5 + resolution: "@types/node@npm:22.16.5" dependencies: undici-types: "npm:~6.21.0" - checksum: 10/96911bc8bcebd32e96d0d6b46214f5b935e47ff3dac1fc6cf6a5256ebdd01dfecf689a41b3e3a1f35385c3341e0b79e0cf42a6409ce81e6b5fb9b3d7317fedbb + checksum: 10/ba45b5c9113cbc5edb12960fcfe7e80db2c998af5c1931264240695b27d756570d92462150b95781bd67a03aa82111cc970ab0f4504eb99213edff8bf425354e languageName: node linkType: hard @@ -9837,6 +9815,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:22.15.0": + version: 22.15.0 + resolution: "@types/node@npm:22.15.0" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10/96911bc8bcebd32e96d0d6b46214f5b935e47ff3dac1fc6cf6a5256ebdd01dfecf689a41b3e3a1f35385c3341e0b79e0cf42a6409ce81e6b5fb9b3d7317fedbb + languageName: node + linkType: hard + "@types/nodemailer@npm:*": version: 6.4.15 resolution: "@types/nodemailer@npm:6.4.15" @@ -9913,13 +9900,6 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*": - version: 6.9.7 - resolution: "@types/qs@npm:6.9.7" - checksum: 10/7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba - languageName: node - linkType: hard - "@types/ramda@npm:~0.30.0": version: 0.30.1 resolution: "@types/ramda@npm:0.30.1" @@ -9929,13 +9909,6 @@ __metadata: languageName: node linkType: hard -"@types/range-parser@npm:*": - version: 1.2.4 - resolution: "@types/range-parser@npm:1.2.4" - checksum: 10/b7c0dfd5080a989d6c8bb0b6750fc0933d9acabeb476da6fe71d8bdf1ab65e37c136169d84148034802f48378ab94e3c37bb4ef7656b2bec2cb9c0f8d4146a95 - languageName: node - linkType: hard - "@types/rbush@npm:4.0.0": version: 4.0.0 resolution: "@types/rbush@npm:4.0.0" @@ -10099,13 +10072,6 @@ __metadata: languageName: node linkType: hard -"@types/retry@npm:0.12.2": - version: 0.12.2 - resolution: "@types/retry@npm:0.12.2" - checksum: 10/e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a - languageName: node - linkType: hard - "@types/semver@npm:7.5.8": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" @@ -10120,36 +10086,6 @@ __metadata: languageName: node linkType: hard -"@types/send@npm:*": - version: 0.17.4 - resolution: "@types/send@npm:0.17.4" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10/28320a2aa1eb704f7d96a65272a07c0bf3ae7ed5509c2c96ea5e33238980f71deeed51d3631927a77d5250e4091b3e66bce53b42d770873282c6a20bb8b0280d - languageName: node - linkType: hard - -"@types/serve-index@npm:^1.9.4": - version: 1.9.4 - resolution: "@types/serve-index@npm:1.9.4" - dependencies: - "@types/express": "npm:*" - checksum: 10/72727c88d54da5b13275ebfb75dcdc4aa12417bbe9da1939e017c4c5f0c906fae843aa4e0fbfe360e7ee9df2f3d388c21abfc488f77ce58693fb57809f8ded92 - languageName: node - linkType: hard - -"@types/serve-static@npm:*, @types/serve-static@npm:^1.15.5": - version: 1.15.5 - resolution: "@types/serve-static@npm:1.15.5" - dependencies: - "@types/http-errors": "npm:*" - "@types/mime": "npm:*" - "@types/node": "npm:*" - checksum: 10/49aa21c367fffe4588fc8c57ea48af0ea7cbadde7418bc53cde85d8bd57fd2a09a293970d9ea86e79f17a87f8adeb3e20da76aab38e1c4d1567931fa15c8af38 - languageName: node - linkType: hard - "@types/sinonjs__fake-timers@npm:8.1.1": version: 8.1.1 resolution: "@types/sinonjs__fake-timers@npm:8.1.1" @@ -10204,15 +10140,6 @@ __metadata: languageName: node linkType: hard -"@types/sockjs@npm:^0.3.36": - version: 0.3.36 - resolution: "@types/sockjs@npm:0.3.36" - dependencies: - "@types/node": "npm:*" - checksum: 10/b4b5381122465d80ea8b158537c00bc82317222d3fb31fd7229ff25b31fa89134abfbab969118da55622236bf3d8fee75759f3959908b5688991f492008f29bc - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -10331,6 +10258,15 @@ __metadata: languageName: node linkType: hard +"@types/wait-on@npm:^5.2.0": + version: 5.3.4 + resolution: "@types/wait-on@npm:5.3.4" + dependencies: + "@types/node": "npm:*" + checksum: 10/2711a9ef4af3995efd442acb806d3bba0a03ef3a7eb07b1734ddd8a42b6682414f6b57ed7536fb6a5df6c6449ca24c3d42924a9d896ae3eb1065b2abbe988a18 + languageName: node + linkType: hard + "@types/webpack-assets-manifest@npm:^5": version: 5.1.4 resolution: "@types/webpack-assets-manifest@npm:5.1.4" @@ -10360,15 +10296,6 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.10": - version: 8.5.10 - resolution: "@types/ws@npm:8.5.10" - dependencies: - "@types/node": "npm:*" - checksum: 10/9b414dc5e0b6c6f1ea4b1635b3568c58707357f68076df9e7cd33194747b7d1716d5189c0dbdd68c8d2521b148e88184cf881bac7429eb0e5c989b001539ed31 - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 20.2.1 resolution: "@types/yargs-parser@npm:20.2.1" @@ -11096,7 +11023,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": +"accepts@npm:~1.3.4": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -11152,12 +11079,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" +"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard @@ -11292,6 +11219,13 @@ __metadata: languageName: node linkType: hard +"anser@npm:^2.1.1": + version: 2.3.2 + resolution: "anser@npm:2.3.2" + checksum: 10/2bfb5fc96533919060bd2f2c199d22011f58e3d0ca48156cec7508ca5f741ea08e38fe634ae3d6902ae44b445148438f2ebcaf09f08ff3b2be20687c19cf4419 + languageName: node + linkType: hard + "ansi-colors@npm:^4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" @@ -11326,7 +11260,7 @@ __metadata: languageName: node linkType: hard -"ansi-html-community@npm:0.0.8, ansi-html-community@npm:^0.0.8": +"ansi-html-community@npm:0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" bin: @@ -11335,15 +11269,6 @@ __metadata: languageName: node linkType: hard -"ansi-html@npm:^0.0.9": - version: 0.0.9 - resolution: "ansi-html@npm:0.0.9" - bin: - ansi-html: bin/ansi-html - checksum: 10/3e83fae364d323d9c453f74a21aa29da68ae152e996c66de45a49a445ea362c4e2e9abce0069558239ff23e3d6ae73b5d27993d631382aa83d85f44b687e0aa1 - languageName: node - linkType: hard - "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -11442,6 +11367,15 @@ __metadata: languageName: node linkType: hard +"append-transform@npm:^2.0.0": + version: 2.0.0 + resolution: "append-transform@npm:2.0.0" + dependencies: + default-require-extensions: "npm:^3.0.0" + checksum: 10/f26f393bf7a428fd1bb18f2758a819830a582243310c5170edb3f98fdc5a535333d02b952f7c2d9b14522bd8ead5b132a0b15000eca18fa9f49172963ebbc231 + languageName: node + linkType: hard + "aproba@npm:2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -11463,6 +11397,13 @@ __metadata: languageName: node linkType: hard +"archy@npm:^1.0.0": + version: 1.0.0 + resolution: "archy@npm:1.0.0" + checksum: 10/d7928049a57988b86df3f4de75ca16a4252ccee591d085c627e649fc54c5ae5daa833f17aa656bd825bd00bc0a2756ae03d2b983050bdbda1046b6d832bf7303 + languageName: node + linkType: hard + "are-docs-informative@npm:^0.0.2": version: 0.0.2 resolution: "are-docs-informative@npm:0.0.2" @@ -11543,13 +11484,6 @@ __metadata: languageName: node linkType: hard -"array-flatten@npm:1.1.1": - version: 1.1.1 - resolution: "array-flatten@npm:1.1.1" - checksum: 10/e13c9d247241be82f8b4ec71d035ed7204baa82fae820d4db6948d30d3c4a9f2b3905eb2eec2b937d4aa3565200bd3a1c500480114cff649fa748747d2a50feb - languageName: node - linkType: hard - "array-ify@npm:^1.0.0": version: 1.0.0 resolution: "array-ify@npm:1.0.0" @@ -11557,17 +11491,19 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa languageName: node linkType: hard @@ -11615,29 +11551,30 @@ __metadata: languageName: node linkType: hard -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10/d9d2f6f27584de92ec7995bc931103e6de722cd2498bdbfc4cba814fc3e52f056050a93be883018811f7c0a35875f5056584a0e940603a5e5934f0279896aebe + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/f9b992fa0775d8f7c97abc91eb7f7b2f0ed8430dd9aeb9fdc2967ac4760cdd7fc2ef7ead6528fef40c7261e4d790e117808ce0d3e7e89e91514d4963a531cd01 languageName: node linkType: hard @@ -11879,14 +11816,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1, axios@npm:^1.7.9, axios@npm:^1.8.3, axios@npm:^1.9.0": - version: 1.10.0 - resolution: "axios@npm:1.10.0" +"axios@npm:^1, axios@npm:^1.6.1, axios@npm:^1.7.9, axios@npm:^1.8.3, axios@npm:^1.9.0": + version: 1.11.0 + resolution: "axios@npm:1.11.0" dependencies: follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10/d43c80316a45611fd395743e15d16ea69a95f2b7f7095f2bb12cb78f9ca0a905194a02e52a3bf4e0db9f85fd1186d6c690410644c10ecd8bb0a468e57c2040e4 + checksum: 10/232df4af7a4e4e07baa84621b9cc4b0c518a757b4eacc7f635c0eb3642cb98dff347326739f24b891b3b4481b7b838c79a3a0c4819c9fbc1fc40232431b9c5dc languageName: node linkType: hard @@ -11975,39 +11912,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.10": - version: 0.4.10 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.10" +"babel-plugin-polyfill-corejs2@npm:^0.4.14": + version: 0.4.14 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.14" dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.1" + "@babel/compat-data": "npm:^7.27.7" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/9fb5e59a3235eba66fb05060b2a3ecd6923084f100df7526ab74b6272347d7adcf99e17366b82df36e592cde4e82fdb7ae24346a990eced76c7d504cac243400 + checksum: 10/8ec00a1b821ccbfcc432630da66e98bc417f5301f4ce665269d50d245a18ad3ce8a8af2a007f28e3defcd555bb8ce65f16b0d4b6d131bd788e2b97d8b8953332 languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.11.0": - version: 0.11.1 - resolution: "babel-plugin-polyfill-corejs3@npm:0.11.1" +"babel-plugin-polyfill-corejs3@npm:^0.13.0": + version: 0.13.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.3" - core-js-compat: "npm:^3.40.0" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/19a2978ee3462cc3b98e7d36e6537bf9fb1fb61f42fd96cb41e9313f2ac6f2c62380d94064366431eff537f342184720fe9bce73eb65fd57c5311d15e8648f62 + checksum: 10/aa36f9a09521404dd0569a4cbd5f88aa4b9abff59508749abde5d09d66c746012fb94ed1e6e2c8be3710939a2a4c6293ee3be889125d7611c93e5897d9e5babd languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.6.1": - version: 0.6.1 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.1" +"babel-plugin-polyfill-regenerator@npm:^0.6.5": + version: 0.6.5 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.5" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.1" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/9df4a8e9939dd419fed3d9ea26594b4479f2968f37c225e1b2aa463001d7721f5537740e6622909d2a570b61cec23256924a1701404fc9d6fd4474d3e845cedb + checksum: 10/ed1932fa9a31e0752fd10ebf48ab9513a654987cab1182890839523cb898559d24ae0578fdc475d9f995390420e64eeaa4b0427045b56949dace3c725bc66dbb languageName: node linkType: hard @@ -12096,13 +12033,6 @@ __metadata: languageName: node linkType: hard -"batch@npm:0.6.1": - version: 0.6.1 - resolution: "batch@npm:0.6.1" - checksum: 10/61f9934c7378a51dce61b915586191078ef7f1c3eca707fdd58b96ff2ff56d9e0af2bdab66b1462301a73c73374239e6542d9821c0af787f3209a23365d07e7f - languageName: node - linkType: hard - "bcrypt-pbkdf@npm:^1.0.0": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -12279,16 +12209,6 @@ __metadata: languageName: node linkType: hard -"bonjour-service@npm:^1.2.1": - version: 1.2.1 - resolution: "bonjour-service@npm:1.2.1" - dependencies: - fast-deep-equal: "npm:^3.1.3" - multicast-dns: "npm:^7.2.5" - checksum: 10/8350d135ab8dd998a829136984d7f74bfc0667b162ab99ac98bae54d72ff7a6003c6fb7911739dfba7c56a113bd6ab06a4d4fe6719b18e66592c345663e7d923 - languageName: node - linkType: hard - "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -12378,7 +12298,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.24.0, browserslist@npm:^4.24.3, browserslist@npm:^4.24.4, browserslist@npm:^4.24.5": +"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.24.0, browserslist@npm:^4.24.4, browserslist@npm:^4.24.5, browserslist@npm:^4.25.1": version: 4.25.1 resolution: "browserslist@npm:4.25.1" dependencies: @@ -12451,15 +12371,6 @@ __metadata: languageName: node linkType: hard -"bundle-name@npm:^4.1.0": - version: 4.1.0 - resolution: "bundle-name@npm:4.1.0" - dependencies: - run-applescript: "npm:^7.0.0" - checksum: 10/1d966c8d2dbf4d9d394e53b724ac756c2414c45c01340b37743621f59cc565a435024b394ddcb62b9b335d1c9a31f4640eb648c3fec7f97ee74dc0694c9beb6c - languageName: node - linkType: hard - "byte-size@npm:8.1.1": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -12474,13 +12385,6 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.0.0": - version: 3.0.0 - resolution: "bytes@npm:3.0.0" - checksum: 10/a2b386dd8188849a5325f58eef69c3b73c51801c08ffc6963eddc9be244089ba32d19347caf6d145c86f315ae1b1fc7061a32b0c1aa6379e6a719090287ed101 - languageName: node - linkType: hard - "bytes@npm:3.1.2, bytes@npm:^3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -12551,6 +12455,18 @@ __metadata: languageName: node linkType: hard +"caching-transform@npm:^4.0.0": + version: 4.0.0 + resolution: "caching-transform@npm:4.0.0" + dependencies: + hasha: "npm:^5.0.0" + make-dir: "npm:^3.0.0" + package-hash: "npm:^4.0.0" + write-file-atomic: "npm:^3.0.0" + checksum: 10/7e7ca628511ab18c86eea1231834d2591de29a13ae771a7d9ab85be8c6e53e45c5a5b0d0d95d4a3274fc4f26c16956a98162e40c191c131204b5d5aa949660b5 + languageName: node + linkType: hard + "calculate-size@npm:1.1.1": version: 1.1.1 resolution: "calculate-size@npm:1.1.1" @@ -12632,7 +12548,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^5.3.1": +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" checksum: 10/e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b @@ -12697,13 +12613,13 @@ __metadata: languageName: node linkType: hard -"centrifuge@npm:5.3.5": - version: 5.3.5 - resolution: "centrifuge@npm:5.3.5" +"centrifuge@npm:5.4.0": + version: 5.4.0 + resolution: "centrifuge@npm:5.4.0" dependencies: events: "npm:^3.3.0" protobufjs: "npm:^7.2.5" - checksum: 10/71eecfaf90707fec428857fa7520f997bb56a9e6104a33b95d3c4b837be183d3f94641ca626874b1ea888dc4f6f983d24b3b777ab6e357359cb8dd3c7b515784 + checksum: 10/251788a36675dcc89906a158b8554f1d36869aba4236c3eeb54fe0ff819fffa6d91e5189b5bb31d48505482f6fdb3425e487f94c79a2e1f316061a229768227a languageName: node linkType: hard @@ -12751,7 +12667,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2, chalk@npm:~4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -12898,7 +12814,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.3.1, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": +"chokidar@npm:^3.3.1, chokidar@npm:^3.5.3": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -12917,12 +12833,12 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^4.0.0": - version: 4.0.1 - resolution: "chokidar@npm:4.0.1" +"chokidar@npm:^4.0.0, chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" dependencies: readdirp: "npm:^4.0.1" - checksum: 10/62749d2173a60cc5632d6c6e0b7024f33aadce47b06d02e55ad03c7b8daaaf2fc85d4296c047473d04387fd992dab9384cc5263c70a3dc3018b7ebecfb5b5217 + checksum: 10/bf2a575ea5596000e88f5db95461a9d59ad2047e939d5a4aac59dd472d126be8f1c1ff3c7654b477cf532d18f42a97279ef80ee847972fd2a25410bf00b80b59 languageName: node linkType: hard @@ -13106,6 +13022,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10/44afbcc29df0899e87595590792a871cd8c4bc7d6ce92832d9ae268d141a77022adafca1aeaeccff618b62a613b8354e57fe22a275c199ec04baf00d381ef6ab + languageName: node + linkType: hard + "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -13365,6 +13292,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^3.0.2": + version: 3.0.2 + resolution: "commander@npm:3.0.2" + checksum: 10/f42053569f5954498246783465b39139917a51284bf3361574c9f731fea27a4bd6452dbb1755cc2d923c7b47dfea67930037c7b7e862288f2c397cec9a74da87 + languageName: node + linkType: hard + "commander@npm:^6.2.0, commander@npm:^6.2.1, commander@npm:~6.2.1": version: 6.2.1 resolution: "commander@npm:6.2.1" @@ -13431,30 +13365,6 @@ __metadata: languageName: node linkType: hard -"compressible@npm:~2.0.16": - version: 2.0.18 - resolution: "compressible@npm:2.0.18" - dependencies: - mime-db: "npm:>= 1.43.0 < 2" - checksum: 10/58321a85b375d39230405654721353f709d0c1442129e9a17081771b816302a012471a9b8f4864c7dbe02eef7f2aaac3c614795197092262e94b409c9be108f0 - languageName: node - linkType: hard - -"compression@npm:^1.7.4": - version: 1.7.4 - resolution: "compression@npm:1.7.4" - dependencies: - accepts: "npm:~1.3.5" - bytes: "npm:3.0.0" - compressible: "npm:~2.0.16" - debug: "npm:2.6.9" - on-headers: "npm:~1.0.2" - safe-buffer: "npm:5.1.2" - vary: "npm:~1.1.2" - checksum: 10/469cd097908fe1d3ff146596d4c24216ad25eabb565c5456660bdcb3a14c82ebc45c23ce56e19fc642746cf407093b55ab9aa1ac30b06883b27c6c736e6383c2 - languageName: node - linkType: hard - "compute-scroll-into-view@npm:^3.1.0": version: 3.1.0 resolution: "compute-scroll-into-view@npm:3.1.0" @@ -13488,13 +13398,6 @@ __metadata: languageName: node linkType: hard -"connect-history-api-fallback@npm:^2.0.0": - version: 2.0.0 - resolution: "connect-history-api-fallback@npm:2.0.0" - checksum: 10/3b26bf4041fdb33deacdcb3af9ae11e9a0b413fb14c95844d74a460b55e407625b364955dcf965c654605cde9d24ad5dad423c489aa430825aab2035859aba0c - languageName: node - linkType: hard - "connect@npm:3.7.0": version: 3.7.0 resolution: "connect@npm:3.7.0" @@ -13539,15 +13442,6 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4": - version: 0.5.4 - resolution: "content-disposition@npm:0.5.4" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720 - languageName: node - linkType: hard - "content-disposition@npm:^1.0.0": version: 1.0.0 resolution: "content-disposition@npm:1.0.0" @@ -13557,7 +13451,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:^1.0.5, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 @@ -13664,12 +13558,10 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0": - version: 1.8.0 - resolution: "convert-source-map@npm:1.8.0" - dependencies: - safe-buffer: "npm:~5.1.1" - checksum: 10/985d974a2d33e1a2543ada51c93e1ba2f73eaed608dc39f229afc78f71dcc4c8b7d7c684aa647e3c6a3a204027444d69e53e169ce94e8d1fa8d7dee80c9c8fed +"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10/dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 languageName: node linkType: hard @@ -13680,13 +13572,6 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a - languageName: node - linkType: hard - "cookie-signature@npm:^1.2.1": version: 1.2.2 resolution: "cookie-signature@npm:1.2.2" @@ -13694,13 +13579,6 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380 - languageName: node - linkType: hard - "cookie@npm:^0.7.1, cookie@npm:^0.7.2, cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" @@ -13733,12 +13611,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.38.1, core-js-compat@npm:^3.40.0": - version: 3.40.0 - resolution: "core-js-compat@npm:3.40.0" +"core-js-compat@npm:^3.38.1, core-js-compat@npm:^3.43.0": + version: 3.44.0 + resolution: "core-js-compat@npm:3.44.0" dependencies: - browserslist: "npm:^4.24.3" - checksum: 10/3dd3d717b3d4ae0d9c2930d39c0f2a21ca6f195fcdd5711bda833557996c4d9f90277eab576423478e95689257e2de8d1a2623d6618084416bd224d10d5df9a4 + browserslist: "npm:^4.25.1" + checksum: 10/41885423aaea9cd543ca821ace9fabfbaa5f5c202d60244a0baf150326db8f52bee6a01a3fd1b8f7e3026e63b311d93c92bc6ad219fe4a2b2f95df01fff0ea1c languageName: node linkType: hard @@ -13749,10 +13627,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:3.40.0": - version: 3.40.0 - resolution: "core-js@npm:3.40.0" - checksum: 10/9c7e7d2839db6c3c7b72725a3ce5edf6dd61fc771e1551e08b5622ca7da330f2f5d3c7dcd6a6e7889baa8d4cc5a909ba75b4add0d1f3da940a2a60a3e0603be5 +"core-js@npm:3.44.0": + version: 3.44.0 + resolution: "core-js@npm:3.44.0" + checksum: 10/759ef4ab0d12c9a6e8a32537d2b0fe64c2d7be40e13e0d7eb9604a970c380d64f37489dee03bd1c286c169e47a69d3ca2a968e8fcde0f78094ea22a20465d763 languageName: node linkType: hard @@ -14282,6 +14160,16 @@ __metadata: languageName: node linkType: hard +"cwd@npm:^0.10.0": + version: 0.10.0 + resolution: "cwd@npm:0.10.0" + dependencies: + find-pkg: "npm:^0.1.2" + fs-exists-sync: "npm:^0.1.0" + checksum: 10/fdb2d1e8e1317e488bf0b7e569a17f929a45c4f2e706820c843a6be460cf8b334c43ac22c22c58c4f6258e501904ec1d1558d67c345002268fb40e3419f909da + languageName: node + linkType: hard + "cypress-file-upload@npm:5.0.8": version: 5.0.8 resolution: "cypress-file-upload@npm:5.0.8" @@ -14292,11 +14180,11 @@ __metadata: linkType: hard "cypress-recurse@npm:^1.35.3": - version: 1.35.3 - resolution: "cypress-recurse@npm:1.35.3" + version: 1.37.1 + resolution: "cypress-recurse@npm:1.37.1" dependencies: humanize-duration: "npm:^3.27.3" - checksum: 10/408781ff538c0ac832bf1ce61b3e6628a6ab5832d9c425e06c4d724eed977dcadec03f234d6f90ea1826f21d440d5f7f971738399770b83358918be16da53d75 + checksum: 10/fd2bbd1613b5cf7dc2127c813f3a700eccad6319ee3d011a61a373591d491eb202fe0113faa5156c1e1d2d9f2663a41ab49240abf842dff217d70854e668316c languageName: node linkType: hard @@ -14859,7 +14747,7 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^1.1.0": +"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" checksum: 10/ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa @@ -14943,20 +14831,12 @@ __metadata: languageName: node linkType: hard -"default-browser-id@npm:^5.0.0": - version: 5.0.0 - resolution: "default-browser-id@npm:5.0.0" - checksum: 10/185bfaecec2c75fa423544af722a3469b20704c8d1942794a86e4364fe7d9e8e9f63241a5b769d61c8151993bc65833a5b959026fa1ccea343b3db0a33aa6deb - languageName: node - linkType: hard - -"default-browser@npm:^5.2.1": - version: 5.2.1 - resolution: "default-browser@npm:5.2.1" +"default-require-extensions@npm:^3.0.0": + version: 3.0.1 + resolution: "default-require-extensions@npm:3.0.1" dependencies: - bundle-name: "npm:^4.1.0" - default-browser-id: "npm:^5.0.0" - checksum: 10/afab7eff7b7f5f7a94d9114d1ec67273d3fbc539edf8c0f80019879d53aa71e867303c6f6d7cffeb10a6f3cfb59d4f963dba3f9c96830b4540cc7339a1bf9840 + strip-bom: "npm:^4.0.0" + checksum: 10/45882fc971dd157faf6716ced04c15cf252c0a2d6f5c5844b66ca49f46ed03396a26cd940771aa569927aee22923a961bab789e74b25aabc94d90742c9dd1217 languageName: node linkType: hard @@ -14987,14 +14867,7 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^3.0.0": - version: 3.0.0 - resolution: "define-lazy-prop@npm:3.0.0" - checksum: 10/f28421cf9ee86eecaf5f3b8fe875f13d7009c2625e97645bfff7a2a49aca678270b86c39f9c32939e5ca7ab96b551377ed4139558c795e076774287ad3af1aa4 - languageName: node - linkType: hard - -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -15051,13 +14924,6 @@ __metadata: languageName: node linkType: hard -"depd@npm:~1.1.2": - version: 1.1.2 - resolution: "depd@npm:1.1.2" - checksum: 10/2ed6966fc14463a9e85451db330ab8ba041efed0b9a1a472dbfc6fbf2f82bab66491915f996b25d8517dddc36c8c74e24c30879b34877f3c4410733444a51d1d - languageName: node - linkType: hard - "deprecation@npm:^2.0.0": version: 2.3.1 resolution: "deprecation@npm:2.3.1" @@ -15109,13 +14975,6 @@ __metadata: languageName: node linkType: hard -"detect-node@npm:^2.0.4": - version: 2.1.0 - resolution: "detect-node@npm:2.1.0" - checksum: 10/832184ec458353e41533ac9c622f16c19f7c02d8b10c303dfd3a756f56be93e903616c0bb2d4226183c9351c15fc0b3dba41a17a2308262afabcfa3776e6ae6e - languageName: node - linkType: hard - "devtools-protocol@npm:0.0.869402": version: 0.0.869402 resolution: "devtools-protocol@npm:0.0.869402" @@ -15158,6 +15017,15 @@ __metadata: languageName: node linkType: hard +"diffable-html@npm:^4.1.0": + version: 4.1.0 + resolution: "diffable-html@npm:4.1.0" + dependencies: + htmlparser2: "npm:^3.9.2" + checksum: 10/3dd0f2a1e956be394a7da47caa9cb593073d91e3eb30d79de00664941f2d68a2549c7e392da1037afb45de1cf37b12e8b7dbaa312d47186061c66c9ce4aaa601 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -15183,15 +15051,6 @@ __metadata: languageName: node linkType: hard -"dns-packet@npm:^5.2.2": - version: 5.4.0 - resolution: "dns-packet@npm:5.4.0" - dependencies: - "@leichtgewicht/ip-codec": "npm:^2.0.1" - checksum: 10/6a3827d59a7c3b9a8f211d6ba1299bb19e8abed838690d88ed5b47d739c3ac8615a6aa2cbef3a3e87bf21f69a10e78e275bc63b9b411b4263afe4b1ada325574 - languageName: node - linkType: hard - "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -15254,6 +15113,16 @@ __metadata: languageName: node linkType: hard +"dom-serializer@npm:0": + version: 0.2.2 + resolution: "dom-serializer@npm:0.2.2" + dependencies: + domelementtype: "npm:^2.0.1" + entities: "npm:^2.0.0" + checksum: 10/376344893e4feccab649a14ca1a46473e9961f40fe62479ea692d4fee4d9df1c00ca8654811a79c1ca7b020096987e1ca4fb4d7f8bae32c1db800a680a0e5d5e + languageName: node + linkType: hard + "dom-serializer@npm:^1.0.1": version: 1.3.2 resolution: "dom-serializer@npm:1.3.2" @@ -15276,6 +15145,13 @@ __metadata: languageName: node linkType: hard +"domelementtype@npm:1, domelementtype@npm:^1.3.1": + version: 1.3.1 + resolution: "domelementtype@npm:1.3.1" + checksum: 10/7893da40218ae2106ec6ffc146b17f203487a52f5228b032ea7aa470e41dfe03e1bd762d0ee0139e792195efda765434b04b43cddcf63207b098f6ae44b36ad6 + languageName: node + linkType: hard + "domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" @@ -15292,6 +15168,15 @@ __metadata: languageName: node linkType: hard +"domhandler@npm:^2.3.0": + version: 2.4.2 + resolution: "domhandler@npm:2.4.2" + dependencies: + domelementtype: "npm:1" + checksum: 10/d8b0303c53c0eda912e45820ef8f6023f8462a724e8b824324f27923970222a250c7569e067de398c4d9ca3ce0f2b2d2818bc632d6fa72956721d6729479a9b9 + languageName: node + linkType: hard + "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0": version: 4.2.2 resolution: "domhandler@npm:4.2.2" @@ -15322,6 +15207,16 @@ __metadata: languageName: node linkType: hard +"domutils@npm:^1.5.1": + version: 1.7.0 + resolution: "domutils@npm:1.7.0" + dependencies: + dom-serializer: "npm:0" + domelementtype: "npm:1" + checksum: 10/8c1d879fd3bbfc0156c970d12ebdf530f541cbda895d7f631b2444d22bbb9d0e5a3a4c3210cffb17708ad67531d7d40e1bef95e915c53a218d268607b66b63c8 + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.6.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -15387,8 +15282,8 @@ __metadata: linkType: hard "downshift@npm:^9.0.6": - version: 9.0.9 - resolution: "downshift@npm:9.0.9" + version: 9.0.10 + resolution: "downshift@npm:9.0.10" dependencies: "@babel/runtime": "npm:^7.24.5" compute-scroll-into-view: "npm:^3.1.0" @@ -15397,7 +15292,7 @@ __metadata: tslib: "npm:^2.6.2" peerDependencies: react: ">=16.12.0" - checksum: 10/6abc7a585f002f0ebaba1ec42f6102b940257f294d0a33bf189387a316afa24254f0d49c4d5d2553a2263b4e42f497297a8c66dab9bebe24b3dce11fa1456d20 + checksum: 10/eb102d7eb18613c292b2b3e2b42ec70eb36ddda4d2bbcb544cc85932359b727ac566de4f3cf66123741d44fe16ac7639ff148abb5a9702e1f70bfab8ec1a3468 languageName: node linkType: hard @@ -15645,6 +15540,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^1.1.1": + version: 1.1.2 + resolution: "entities@npm:1.1.2" + checksum: 10/4a707022f4e932060f03df2526be55d085a2576fe534421e5b22bc62abb0d1f04241c171f9981e3d7baa4f4160606cad72a2f7eb01b6a25e279e3f31a2be4bf2 + languageName: node + linkType: hard + "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -15741,26 +15643,26 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": - version: 1.23.9 - resolution: "es-abstract@npm:1.23.9" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" + es-object-atoms: "npm:^1.1.1" es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.0" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -15772,21 +15674,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.3" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -15795,8 +15700,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/31a321966d760d88fc2ed984104841b42f4f24fc322b246002b9be0af162e03803ee41fcc3cf8be89e07a27ba3033168f877dd983703cb81422ffe5322a27582 + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf languageName: node linkType: hard @@ -15866,12 +15771,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/6d3bf91f658a27cc7217cd32b407a0d714393a84d125ad576319b9e83a893bea165cf41270c29e9ceaa56d3cf41608945d7e2a2c31fd51c0009b0c31402b91c7 + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba languageName: node linkType: hard @@ -15898,6 +15803,13 @@ __metadata: languageName: node linkType: hard +"es6-error@npm:^4.0.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10/48483c25701dc5a6376f39bbe2eaf5da0b505607ec5a98cd3ade472c1939242156660636e2e508b33211e48e88b132d245341595c067bd4a95ac79fa7134da06 + languageName: node + linkType: hard + "es6-object-assign@npm:^1.1.0": version: 1.1.0 resolution: "es6-object-assign@npm:1.1.0" @@ -15950,36 +15862,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.25.6, esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.6 - resolution: "esbuild@npm:0.25.6" +"esbuild@npm:0.25.8, esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": + version: 0.25.8 + resolution: "esbuild@npm:0.25.8" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.6" - "@esbuild/android-arm": "npm:0.25.6" - "@esbuild/android-arm64": "npm:0.25.6" - "@esbuild/android-x64": "npm:0.25.6" - "@esbuild/darwin-arm64": "npm:0.25.6" - "@esbuild/darwin-x64": "npm:0.25.6" - "@esbuild/freebsd-arm64": "npm:0.25.6" - "@esbuild/freebsd-x64": "npm:0.25.6" - "@esbuild/linux-arm": "npm:0.25.6" - "@esbuild/linux-arm64": "npm:0.25.6" - "@esbuild/linux-ia32": "npm:0.25.6" - "@esbuild/linux-loong64": "npm:0.25.6" - "@esbuild/linux-mips64el": "npm:0.25.6" - "@esbuild/linux-ppc64": "npm:0.25.6" - "@esbuild/linux-riscv64": "npm:0.25.6" - "@esbuild/linux-s390x": "npm:0.25.6" - "@esbuild/linux-x64": "npm:0.25.6" - "@esbuild/netbsd-arm64": "npm:0.25.6" - "@esbuild/netbsd-x64": "npm:0.25.6" - "@esbuild/openbsd-arm64": "npm:0.25.6" - "@esbuild/openbsd-x64": "npm:0.25.6" - "@esbuild/openharmony-arm64": "npm:0.25.6" - "@esbuild/sunos-x64": "npm:0.25.6" - "@esbuild/win32-arm64": "npm:0.25.6" - "@esbuild/win32-ia32": "npm:0.25.6" - "@esbuild/win32-x64": "npm:0.25.6" + "@esbuild/aix-ppc64": "npm:0.25.8" + "@esbuild/android-arm": "npm:0.25.8" + "@esbuild/android-arm64": "npm:0.25.8" + "@esbuild/android-x64": "npm:0.25.8" + "@esbuild/darwin-arm64": "npm:0.25.8" + "@esbuild/darwin-x64": "npm:0.25.8" + "@esbuild/freebsd-arm64": "npm:0.25.8" + "@esbuild/freebsd-x64": "npm:0.25.8" + "@esbuild/linux-arm": "npm:0.25.8" + "@esbuild/linux-arm64": "npm:0.25.8" + "@esbuild/linux-ia32": "npm:0.25.8" + "@esbuild/linux-loong64": "npm:0.25.8" + "@esbuild/linux-mips64el": "npm:0.25.8" + "@esbuild/linux-ppc64": "npm:0.25.8" + "@esbuild/linux-riscv64": "npm:0.25.8" + "@esbuild/linux-s390x": "npm:0.25.8" + "@esbuild/linux-x64": "npm:0.25.8" + "@esbuild/netbsd-arm64": "npm:0.25.8" + "@esbuild/netbsd-x64": "npm:0.25.8" + "@esbuild/openbsd-arm64": "npm:0.25.8" + "@esbuild/openbsd-x64": "npm:0.25.8" + "@esbuild/openharmony-arm64": "npm:0.25.8" + "@esbuild/sunos-x64": "npm:0.25.8" + "@esbuild/win32-arm64": "npm:0.25.8" + "@esbuild/win32-ia32": "npm:0.25.8" + "@esbuild/win32-x64": "npm:0.25.8" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -16035,7 +15947,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/b1c94893d53e39f3be02493c3a082bc9d090ca1702bf6017967ace1391d31e596da4dfd87e02efef3b7b4f0426918dbc5aa6909420a6ba42d51a872266ab6f2e + checksum: 10/9897411732768e652d90fa5dfadae965e8f420d24e5f23fa0604331a1441769e2c7ee4e41ca53e926f1fb51a53af52e01fc9070fdc1a4edf3e9ec9208ee41273 languageName: node linkType: hard @@ -16121,50 +16033,50 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.12.0": - version: 2.12.0 - resolution: "eslint-module-utils@npm:2.12.0" +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee + checksum: 10/bd25d6610ec3abaa50e8f1beb0119541562bbb8dd02c035c7e887976fe1e0c5dd8175f4607ca8d86d1146df24d52a071bd3d1dd329f6902bd58df805a8ca16d3 languageName: node linkType: hard "eslint-plugin-import@npm:^2.31.0": - version: 2.31.0 - resolution: "eslint-plugin-import@npm:2.31.0" + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" dependencies: "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.0" + eslint-module-utils: "npm:^2.12.1" hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" + is-core-module: "npm:^2.16.1" is-glob: "npm:^4.0.3" minimatch: "npm:^3.1.2" object.fromentries: "npm:^2.0.8" object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" + object.values: "npm:^1.2.1" semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.8" + string.prototype.trimend: "npm:^1.0.9" tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c + checksum: 10/1bacf4967e9ebf99e12176a795f0d6d3a87d1c9a030c2207f27b267e10d96a1220be2647504c7fc13ab543cdf13ffef4b8f5620e0447032dba4ff0d3922f7c9e languageName: node linkType: hard "eslint-plugin-jest-dom@npm:^5.4.0": - version: 5.4.0 - resolution: "eslint-plugin-jest-dom@npm:5.4.0" + version: 5.5.0 + resolution: "eslint-plugin-jest-dom@npm:5.5.0" dependencies: "@babel/runtime": "npm:^7.16.3" requireindex: "npm:^1.2.0" @@ -16174,7 +16086,7 @@ __metadata: peerDependenciesMeta: "@testing-library/dom": optional: true - checksum: 10/b8b0b0249d066658a75723892bc6f52d6bcf03ff0a69fc5020548c49f740613a8f3acce647f8f04b292606d2bd0ab3372a695aa3d90b4efb19e71870bbddf637 + checksum: 10/73aaaa6117abbe3b197bc6b1e45839aaa9c2b4c86e7efbc4ff29f03318ec7f019a8e32652c06c61f06fdb22fb296c068a802a268e7b88aa6b71d3477d949b2c6 languageName: node linkType: hard @@ -16347,13 +16259,13 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^8.1.0, eslint-scope@npm:^8.2.0": - version: 8.2.0 - resolution: "eslint-scope@npm:8.2.0" +"eslint-scope@npm:^8.1.0, eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10/cd9ab60d5a68f3a0fcac04d1cff5a7383d0f331964d5f1c446259123caec5b3ccc542284d07846e4f4d1389da77750821cc9a6e1ce18558c674977351666f9a6 + checksum: 10/e8e611701f65375e034c62123946e628894f0b54aa8cb11abe224816389abe5cd74cf16b62b72baa36504f22d1a958b9b8b0169b82397fe2e7997674c0d09b06 languageName: node linkType: hard @@ -16387,20 +16299,21 @@ __metadata: languageName: node linkType: hard -"eslint@npm:9.19.0": - version: 9.19.0 - resolution: "eslint@npm:9.19.0" +"eslint@npm:9.32.0": + version: 9.32.0 + resolution: "eslint@npm:9.32.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.19.0" - "@eslint/core": "npm:^0.10.0" - "@eslint/eslintrc": "npm:^3.2.0" - "@eslint/js": "npm:9.19.0" - "@eslint/plugin-kit": "npm:^0.2.5" + "@eslint/config-array": "npm:^0.21.0" + "@eslint/config-helpers": "npm:^0.3.0" + "@eslint/core": "npm:^0.15.0" + "@eslint/eslintrc": "npm:^3.3.1" + "@eslint/js": "npm:9.32.0" + "@eslint/plugin-kit": "npm:^0.3.4" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.4.1" + "@humanwhocodes/retry": "npm:^0.4.2" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -16408,9 +16321,9 @@ __metadata: cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.2.0" - eslint-visitor-keys: "npm:^4.2.0" - espree: "npm:^10.3.0" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -16432,18 +16345,18 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10/850d19fd6a34702d1e3d9bdad6aef84a20a5c2de006a8fa6380843384b13944b180232ddd74b8725ffcdf8f296399037f0e8eb4783d5f7393f13c059112b843d + checksum: 10/0f8cda1fa09ae188dd90bab21212f5cfca92373b93e91361cf4e2b511b5554d6cca31153dd11f0566210fb97a78b9267ed3d207607788b81880d19effe531085 languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.1.0, espree@npm:^10.3.0": - version: 10.3.0 - resolution: "espree@npm:10.3.0" +"espree@npm:^10.0.1, espree@npm:^10.1.0, espree@npm:^10.3.0, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" dependencies: - acorn: "npm:^8.14.0" + acorn: "npm:^8.15.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/3412d44d4204c9e29d6b5dd0277400cfa0cd68495dc09eae1b9ce79d0c8985c1c5cc09cb9ba32a1cd963f48a49b0c46bdb7736afe395a300aa6bb1c0d86837e8 + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/9b355b32dbd1cc9f57121d5ee3be258fab87ebeb7c83fc6c02e5af1a74fc8c5ba79fe8c663e69ea112c3e84a1b95e6a2067ac4443ee7813bb85ac7581acb8bf9 languageName: node linkType: hard @@ -16653,6 +16566,15 @@ __metadata: languageName: node linkType: hard +"expand-tilde@npm:^1.2.2": + version: 1.2.2 + resolution: "expand-tilde@npm:1.2.2" + dependencies: + os-homedir: "npm:^1.0.1" + checksum: 10/18051cd104977bc06e2bb1347db9959b90504437beea0de6fd287a3c8c58b41e2330337bd189cfca2ee4be6bda9bf045f8c07daf23e622f85eb6ee1c420619a0 + languageName: node + linkType: hard + "expand-tilde@npm:^2.0.0, expand-tilde@npm:^2.0.2": version: 2.0.2 resolution: "expand-tilde@npm:2.0.2" @@ -16662,6 +16584,13 @@ __metadata: languageName: node linkType: hard +"expect-playwright@npm:^0.8.0": + version: 0.8.0 + resolution: "expect-playwright@npm:0.8.0" + checksum: 10/cf8f7f1db819debfb1457b2e56509f838ac4e29c832790f665cf8db7baf21af8271e0ca97849b3998040780c6848533be2afcd262c75edf1ca98805bc2b51b35 + languageName: node + linkType: hard + "expect@npm:^29.0.0, expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -16700,45 +16629,6 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.21.2": - version: 4.21.2 - resolution: "express@npm:4.21.2" - dependencies: - accepts: "npm:~1.3.8" - array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" - content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:1.0.6" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.3" - methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.12" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" - range-parser: "npm:~1.2.1" - safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:~1.6.18" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1 - languageName: node - linkType: hard - "express@npm:^5.0.1": version: 5.1.0 resolution: "express@npm:5.1.0" @@ -16952,15 +16842,6 @@ __metadata: languageName: node linkType: hard -"faye-websocket@npm:^0.11.3": - version: 0.11.4 - resolution: "faye-websocket@npm:0.11.4" - dependencies: - websocket-driver: "npm:>=0.5.1" - checksum: 10/22433c14c60925e424332d2794463a8da1c04848539b5f8db5fced62a7a7c71a25335a4a8b37334e3a32318835e2b87b1733d008561964121c4a0bd55f0878c3 - languageName: node - linkType: hard - "faye-websocket@npm:~0.10.0": version: 0.10.0 resolution: "faye-websocket@npm:0.10.0" @@ -17108,21 +16989,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b - languageName: node - linkType: hard - "finalhandler@npm:^2.1.0": version: 2.1.0 resolution: "finalhandler@npm:2.1.0" @@ -17137,7 +17003,7 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^3.3.1": +"find-cache-dir@npm:^3.2.0, find-cache-dir@npm:^3.3.1": version: 3.3.2 resolution: "find-cache-dir@npm:3.3.2" dependencies: @@ -17158,6 +17024,38 @@ __metadata: languageName: node linkType: hard +"find-file-up@npm:^0.1.2": + version: 0.1.3 + resolution: "find-file-up@npm:0.1.3" + dependencies: + fs-exists-sync: "npm:^0.1.0" + resolve-dir: "npm:^0.1.0" + checksum: 10/95475fee7b727266ec65312527c580eb4f01884592620296cf7859e72cce7f4f6a667c964ad6feeec53fb72a7c3991805532ed7a53d8224e9a1ccd88479cabce + languageName: node + linkType: hard + +"find-pkg@npm:^0.1.2": + version: 0.1.2 + resolution: "find-pkg@npm:0.1.2" + dependencies: + find-file-up: "npm:^0.1.2" + checksum: 10/cd797bfa7dd419849559312cdd3aec767c39939e552daa92e53ff6b61108f331eb2c800d20a5973631eb894ea36c13dded01a868b10f457a685e0ae87a1746e1 + languageName: node + linkType: hard + +"find-process@npm:^1.4.4": + version: 1.4.11 + resolution: "find-process@npm:1.4.11" + dependencies: + chalk: "npm:~4.1.2" + commander: "npm:^12.1.0" + loglevel: "npm:^1.9.2" + bin: + find-process: bin/find-process.js + checksum: 10/95f7106877c86fc09804b29339cfac518a43228c1b5f4a4e9442e57ac5538369fbeafb746fa96390ecd7be6f5a9119b58c952b5c96a0cb9f2490ef19298dec1e + languageName: node + linkType: hard + "find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" @@ -17303,12 +17201,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: "npm:^1.1.3" - checksum: 10/fdac0cde1be35610bd635ae958422e8ce0cc1313e8d32ea6d34cfda7b60850940c1fd07c36456ad76bd9c24aef6ff5e03b02beb58c83af5ef6c968a64eada676 + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f languageName: node linkType: hard @@ -17328,6 +17226,16 @@ __metadata: languageName: node linkType: hard +"foreground-child@npm:^2.0.0": + version: 2.0.0 + resolution: "foreground-child@npm:2.0.0" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10/f36574ad8e19d69ce06fceac7d86161b863968e4ba292c14b7b40e5c464e3e9bcd7711250d33427d95cc2bb0d48cf101df9687433dbbc7fd3c7e4f595be8305e + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -17345,13 +17253,13 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:9.0.2": - version: 9.0.2 - resolution: "fork-ts-checker-webpack-plugin@npm:9.0.2" +"fork-ts-checker-webpack-plugin@npm:9.1.0": + version: 9.1.0 + resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" dependencies: "@babel/code-frame": "npm:^7.16.7" chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" + chokidar: "npm:^4.0.1" cosmiconfig: "npm:^8.2.0" deepmerge: "npm:^4.2.2" fs-extra: "npm:^10.0.0" @@ -17364,7 +17272,7 @@ __metadata: peerDependencies: typescript: ">3.6.0" webpack: ^5.11.0 - checksum: 10/3399ea114d9397da5e51baaa1cf2ca704f4616c8255df672889cafc2a823f77e8bf9913133852d29d676657b1d40e4a2fba8eb9fafee09e00faeda924289220e + checksum: 10/1d24387224f7d49a17f7e44c9150971172f34ae30c4b1f581b8af967e73e8f36a434ed56f78aa45fd8cf0833c73a1b020102cc61070d7dc630b70c21c9770a1b languageName: node linkType: hard @@ -17391,15 +17299,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:~4.0.0": - version: 4.0.2 - resolution: "form-data@npm:4.0.2" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4, form-data@npm:~4.0.0": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb + checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb languageName: node linkType: hard @@ -17445,6 +17354,13 @@ __metadata: languageName: node linkType: hard +"fromentries@npm:^1.2.0": + version: 1.3.2 + resolution: "fromentries@npm:1.3.2" + checksum: 10/10d6e07d289db102c0c1eaf5c3e3fa55ddd6b50033d7de16d99a7cd89f1e1a302dfadb26457031f9bb5d2ed95a179aaf0396092dde5abcae06e8a2f0476826be + languageName: node + linkType: hard + "front-matter@npm:^4.0.2": version: 4.0.2 resolution: "front-matter@npm:4.0.2" @@ -17461,6 +17377,13 @@ __metadata: languageName: node linkType: hard +"fs-exists-sync@npm:^0.1.0": + version: 0.1.0 + resolution: "fs-exists-sync@npm:0.1.0" + checksum: 10/850a0d6e4c03a7bd2fd25043f77cd9d6be9c3b48bb99308bcfe9c94f3f92f65f2cd3fa036e13a1b0ba7a46d2e58792f53e578f01d75fbdcd56baeb9eed63b705 + languageName: node + linkType: hard + "fs-extra@npm:^10.0.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -17677,7 +17600,7 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.5": +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 @@ -17988,7 +17911,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4": +"glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -18023,6 +17946,16 @@ __metadata: languageName: node linkType: hard +"global-modules@npm:^0.2.3": + version: 0.2.3 + resolution: "global-modules@npm:0.2.3" + dependencies: + global-prefix: "npm:^0.1.4" + is-windows: "npm:^0.2.0" + checksum: 10/3801788df54897d994c9c8f3d09f253d1379cd879ae61fcddbcc3ecdfdf6fe23a1edb983e8d4dd24cebf7e49823752e1cd29a2d33bdb4de587de8b4a85b17e24 + languageName: node + linkType: hard + "global-modules@npm:^1.0.0": version: 1.0.0 resolution: "global-modules@npm:1.0.0" @@ -18043,6 +17976,18 @@ __metadata: languageName: node linkType: hard +"global-prefix@npm:^0.1.4": + version: 0.1.5 + resolution: "global-prefix@npm:0.1.5" + dependencies: + homedir-polyfill: "npm:^1.0.0" + ini: "npm:^1.3.4" + is-windows: "npm:^0.2.0" + which: "npm:^1.2.12" + checksum: 10/ea1b818a1851655ebb2341cdd5446da81c25f31ca6f0ac358a234cbed5442edc1bfa5628771466988d67d9fcc6ad09ca0e68a8d3d7e3d92f7de3aec87020e183 + languageName: node + linkType: hard + "global-prefix@npm:^1.0.1": version: 1.0.2 resolution: "global-prefix@npm:1.0.2" @@ -18067,13 +18012,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 - languageName: node - linkType: hard - "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -18194,9 +18132,9 @@ __metadata: resolution: "grafana@workspace:." dependencies: "@arethetypeswrong/cli": "npm:^0.18.2" - "@babel/core": "npm:7.26.10" - "@babel/preset-env": "npm:7.26.9" - "@babel/runtime": "npm:7.27.0" + "@babel/core": "npm:7.28.0" + "@babel/preset-env": "npm:7.28.0" + "@babel/runtime": "npm:7.28.2" "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" "@bsull/augurs": "npm:^0.10.0" @@ -18206,10 +18144,11 @@ __metadata: "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.27.13" + "@floating-ui/react": "npm:0.27.14" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/alerting": "workspace:*" + "@grafana/assistant": "npm:0.0.10" "@grafana/aws-sdk": "npm:0.7.1" "@grafana/azure-sdk": "npm:0.0.7" "@grafana/data": "workspace:*" @@ -18226,7 +18165,7 @@ __metadata: "@grafana/llm": "npm:0.22.1" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-e2e": "npm:2.0.0" + "@grafana/plugin-e2e": "npm:2.1.7" "@grafana/plugin-ui": "npm:0.10.7" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" @@ -18253,8 +18192,8 @@ __metadata: "@opentelemetry/api": "npm:1.9.0" "@opentelemetry/exporter-collector": "npm:0.25.0" "@opentelemetry/semantic-conventions": "npm:1.36.0" - "@playwright/test": "npm:1.52.0" - "@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.15" + "@playwright/test": "npm:1.54.1" + "@pmmmwh/react-refresh-webpack-plugin": "npm:0.6.1" "@popperjs/core": "npm:2.11.8" "@react-aria/dialog": "npm:3.5.27" "@react-aria/focus": "npm:3.20.5" @@ -18274,7 +18213,7 @@ __metadata: "@swc/helpers": "npm:0.5.15" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.2.0" + "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" @@ -18300,7 +18239,7 @@ __metadata: "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" "@types/lucene": "npm:^2" - "@types/node": "npm:22.15.0" + "@types/node": "npm:22.16.5" "@types/node-forge": "npm:^1" "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0" "@types/pluralize": "npm:^0.0.33" @@ -18344,7 +18283,7 @@ __metadata: blob-polyfill: "npm:9.0.20240710" brace: "npm:0.11.1" browserslist: "npm:^4.21.4" - centrifuge: "npm:5.3.5" + centrifuge: "npm:5.4.0" chance: "npm:^1.1.13" chrome-remote-interface: "npm:0.33.3" classnames: "npm:2.5.1" @@ -18354,7 +18293,7 @@ __metadata: common-tags: "npm:1.8.2" confusing-browser-globals: "npm:^1.0.11" copy-webpack-plugin: "npm:12.0.2" - core-js: "npm:3.40.0" + core-js: "npm:3.44.0" crashme: "npm:0.0.15" croner: "npm:^9.0.0" css-loader: "npm:7.1.2" @@ -18369,10 +18308,10 @@ __metadata: date-fns: "npm:4.1.0" debounce-promise: "npm:3.1.2" diff: "npm:^8.0.0" - esbuild: "npm:0.25.6" + esbuild: "npm:0.25.8" esbuild-loader: "npm:4.3.0" esbuild-plugin-browserslist: "npm:^1.0.0" - eslint: "npm:9.19.0" + eslint: "npm:9.32.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.31.0" eslint-plugin-jest: "npm:28.11.0" @@ -18392,7 +18331,7 @@ __metadata: fast-json-patch: "npm:3.1.1" file-saver: "npm:2.0.5" fishery: "npm:^2.2.2" - fork-ts-checker-webpack-plugin: "npm:9.0.2" + fork-ts-checker-webpack-plugin: "npm:9.1.0" glob: "npm:11.0.3" history: "npm:4.10.1" html-loader: "npm:5.1.0" @@ -18430,7 +18369,7 @@ __metadata: lru-cache: "npm:11.1.0" lru-memoize: "npm:^1.1.0" lucene: "npm:^2.1.1" - marked: "npm:16.0.0" + marked: "npm:16.1.1" memoize-one: "npm:6.0.0" micro-memoize: "npm:^4.1.2" mini-css-extract-plugin: "npm:2.9.2" @@ -18446,7 +18385,7 @@ __metadata: node-forge: "npm:^1.3.1" node-notifier: "npm:10.0.1" nx: "npm:20.7.1" - ol: "npm:10.6.0" + ol: "npm:10.6.1" ol-ext: "npm:4.0.33" openapi-types: "npm:^12.1.3" pa11y-ci: "npm:^3.1.0" @@ -18457,7 +18396,7 @@ __metadata: postcss-loader: "npm:8.1.1" postcss-reporter: "npm:7.1.0" postcss-scss: "npm:4.0.9" - prettier: "npm:3.4.2" + prettier: "npm:3.6.2" prismjs: "npm:1.30.0" publint: "npm:^0.3.12" rc-slider: "npm:11.1.8" @@ -18502,7 +18441,7 @@ __metadata: rimraf: "npm:6.0.1" rxjs: "npm:7.8.2" sass: "npm:1.83.4" - sass-loader: "npm:16.0.4" + sass-loader: "npm:16.0.5" selecto: "npm:1.26.3" semver: "npm:7.7.2" slate: "npm:0.47.9" @@ -18515,7 +18454,7 @@ __metadata: swagger-ui-react: "npm:5.27.0" symbol-observable: "npm:4.0.0" systemjs: "npm:6.15.1" - terser-webpack-plugin: "npm:5.3.11" + terser-webpack-plugin: "npm:5.3.14" testing-library-selector: "npm:0.3.1" tinycolor2: "npm:1.6.0" tracelib: "npm:1.0.1" @@ -18527,14 +18466,13 @@ __metadata: typescript: "npm:5.8.3" uplot: "npm:1.6.32" uuid: "npm:11.1.0" - vis-data: "npm:^7.1.10" - vis-network: "npm:9.1.13" + vis-data: "npm:^8.0.0" + vis-network: "npm:10.0.1" webpack: "npm:5.97.1" webpack-assets-manifest: "npm:^5.1.0" webpack-cli: "npm:6.0.1" - webpack-dev-server: "npm:5.2.1" webpack-livereload-plugin: "npm:3.0.2" - webpack-manifest-plugin: "npm:5.0.0" + webpack-manifest-plugin: "npm:5.0.1" webpack-merge: "npm:6.0.1" webpack-subresource-integrity: "npm:^5.2.0-rc.1" webpackbar: "npm:^7.0.0" @@ -18543,7 +18481,7 @@ __metadata: yargs: "npm:^17.5.1" zod: "npm:^3.25.55" dependenciesMeta: - prettier@3.4.2: + prettier@3.6.2: unplugged: true languageName: unknown linkType: soft @@ -18587,13 +18525,6 @@ __metadata: languageName: node linkType: hard -"handle-thing@npm:^2.0.0": - version: 2.0.1 - resolution: "handle-thing@npm:2.0.1" - checksum: 10/441ec98b07f26819c70c702f6c874088eebeb551b242fe8fae4eab325746b82bf84ae7a1f6419547698accb3941fa26806c5f5f93c50e19f90e499065a711d61 - languageName: node - linkType: hard - "handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": version: 4.7.8 resolution: "handlebars@npm:4.7.8" @@ -18688,6 +18619,16 @@ __metadata: languageName: node linkType: hard +"hasha@npm:^5.0.0": + version: 5.2.2 + resolution: "hasha@npm:5.2.2" + dependencies: + is-stream: "npm:^2.0.0" + type-fest: "npm:^0.8.0" + checksum: 10/06cc474bed246761ff61c19d629977eb5f53fa817be4313a255a64ae0f433e831a29e83acb6555e3f4592b348497596f1d1653751008dda4f21c9c21ca60ac5a + languageName: node + linkType: hard + "hasown@npm:^2.0.0, hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -18829,7 +18770,7 @@ __metadata: languageName: node linkType: hard -"homedir-polyfill@npm:^1.0.1": +"homedir-polyfill@npm:^1.0.0, homedir-polyfill@npm:^1.0.1": version: 1.0.3 resolution: "homedir-polyfill@npm:1.0.3" dependencies: @@ -18877,18 +18818,6 @@ __metadata: languageName: node linkType: hard -"hpack.js@npm:^2.1.6": - version: 2.1.6 - resolution: "hpack.js@npm:2.1.6" - dependencies: - inherits: "npm:^2.0.1" - obuf: "npm:^1.0.0" - readable-stream: "npm:^2.0.1" - wbuf: "npm:^1.1.0" - checksum: 10/6910e4b9d943a78fd8e84ac42729fdab9bd406789d6204ad160af9dc5aa4750fc01f208249bf7116c11dc0678207a387b4ade24e4b628b95385b251ceeeb719c - languageName: node - linkType: hard - "html-element-attributes@npm:^1.0.0": version: 1.3.1 resolution: "html-element-attributes@npm:1.3.1" @@ -19022,6 +18951,20 @@ __metadata: languageName: node linkType: hard +"htmlparser2@npm:^3.9.2": + version: 3.10.1 + resolution: "htmlparser2@npm:3.10.1" + dependencies: + domelementtype: "npm:^1.3.1" + domhandler: "npm:^2.3.0" + domutils: "npm:^1.5.1" + entities: "npm:^1.1.1" + inherits: "npm:^2.0.1" + readable-stream: "npm:^3.1.1" + checksum: 10/d5297fe76c0d6b0f35f39781417eb560ef12fa121953578083f3f2b240c74d5c35a38185689d181b6a82b66a3025436f14aa3413b94f3cd50ba15733f2f72389 + languageName: node + linkType: hard + "htmlparser2@npm:^6.1.0": version: 6.1.0 resolution: "htmlparser2@npm:6.1.0" @@ -19065,13 +19008,6 @@ __metadata: languageName: node linkType: hard -"http-deceiver@npm:^1.2.7": - version: 1.2.7 - resolution: "http-deceiver@npm:1.2.7" - checksum: 10/9ae293b0acbfad6ed45d52c1f85f58ab062465872fd9079c80d78c6527634002d73c2a9d8c0296cc12d178a0b689bb5291d9979aad3ce71ab17a7517588adbf7 - languageName: node - linkType: hard - "http-errors@npm:2.0.0, http-errors@npm:^2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" @@ -19085,18 +19021,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~1.6.2": - version: 1.6.3 - resolution: "http-errors@npm:1.6.3" - dependencies: - depd: "npm:~1.1.2" - inherits: "npm:2.0.3" - setprototypeof: "npm:1.1.0" - statuses: "npm:>= 1.4.0 < 2" - checksum: 10/e48732657ea0b4a09853d2696a584fa59fa2a8c1ba692af7af3137b5491a997d7f9723f824e7e08eb6a87098532c09ce066966ddf0f9f3dd30905e52301acadb - languageName: node - linkType: hard - "http-parser-js@npm:>=0.5.1": version: 0.5.6 resolution: "http-parser-js@npm:0.5.6" @@ -19136,24 +19060,6 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.7": - version: 2.0.9 - resolution: "http-proxy-middleware@npm:2.0.9" - dependencies: - "@types/http-proxy": "npm:^1.17.8" - http-proxy: "npm:^1.18.1" - is-glob: "npm:^4.0.1" - is-plain-obj: "npm:^3.0.0" - micromatch: "npm:^4.0.2" - peerDependencies: - "@types/express": ^4.17.13 - peerDependenciesMeta: - "@types/express": - optional: true - checksum: 10/4ece416a91d52e96f8136c5f4abfbf7ac2f39becbad21fa8b158a12d7e7d8f808287ff1ae342b903fd1f15f2249dee87fabc09e1f0e73106b83331c496d67660 - languageName: node - linkType: hard - "http-proxy@npm:^1.18.1": version: 1.18.1 resolution: "http-proxy@npm:1.18.1" @@ -19327,16 +19233,16 @@ __metadata: linkType: hard "i18next@npm:^23.5.1 || ^24.2.0": - version: 24.2.3 - resolution: "i18next@npm:24.2.3" + version: 24.2.2 + resolution: "i18next@npm:24.2.2" dependencies: - "@babel/runtime": "npm:^7.26.10" + "@babel/runtime": "npm:^7.23.2" peerDependencies: typescript: ^5 peerDependenciesMeta: typescript: optional: true - checksum: 10/6c73d964f2a98b1aa2c2717fe6da66fc265bcbbc5fcd52b2bfff51ff013d30d4f7d7449c4eb7f464d27af43e2e73f2e7f1d46a144d731bd3bdb1385d4c199e4c + checksum: 10/f66ed9e56d9412e59502f5df39163631daf9f1264774732fb21edbd66a528ca7a6b67dc2e2aec95683c6c7956e42c651587a54bd8ee082bd12008880ce6cd326 languageName: node linkType: hard @@ -19555,13 +19461,6 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 10/8771303d66c51be433b564427c16011a8e3fbc3449f1f11ea50efb30a4369495f1d0e89f0fc12bdec0bd7e49102ced5d137e031d39ea09821cb3c717fcf21e69 - languageName: node - linkType: hard - "ini@npm:2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -19713,13 +19612,6 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:^2.1.0": - version: 2.1.0 - resolution: "ipaddr.js@npm:2.1.0" - checksum: 10/42c16d95cf451399707c2c46e605b88db1ea2b1477b25774b5a7ee96852b0bb1efdc01adbff01fedbe702ff246e1aca5c5e915a6f5a1f1485233a5f7c2eb73c2 - languageName: node - linkType: hard - "ipv6-normalize@npm:1.0.1": version: 1.0.1 resolution: "ipv6-normalize@npm:1.0.1" @@ -19835,7 +19727,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": +"is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 @@ -19853,12 +19745,12 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.5.0": - version: 2.15.1 - resolution: "is-core-module@npm:2.15.1" +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1, is-core-module@npm:^2.5.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" dependencies: hasown: "npm:^2.0.2" - checksum: 10/77316d5891d5743854bcef2cd2f24c5458fb69fbc9705c12ca17d54a2017a67d0693bbf1ba8c77af376c0eef6bf6d1b27a4ab08e4db4e69914c3789bdf2ceec5 + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb languageName: node linkType: hard @@ -19906,15 +19798,6 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^3.0.0": - version: 3.0.0 - resolution: "is-docker@npm:3.0.0" - bin: - is-docker: cli.js - checksum: 10/b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90 - languageName: node - linkType: hard - "is-extendable@npm:^1.0.0": version: 1.0.1 resolution: "is-extendable@npm:1.0.1" @@ -20009,17 +19892,6 @@ __metadata: languageName: node linkType: hard -"is-inside-container@npm:^1.0.0": - version: 1.0.0 - resolution: "is-inside-container@npm:1.0.0" - dependencies: - is-docker: "npm:^3.0.0" - bin: - is-inside-container: cli.js - checksum: 10/c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03 - languageName: node - linkType: hard - "is-installed-globally@npm:~0.4.0": version: 0.4.0 resolution: "is-installed-globally@npm:0.4.0" @@ -20082,10 +19954,10 @@ __metadata: languageName: node linkType: hard -"is-network-error@npm:^1.0.0": - version: 1.0.1 - resolution: "is-network-error@npm:1.0.1" - checksum: 10/165d61500c4186c62db5a3a693d6bfa14ca40fe9b471ef4cd4f27b20ef6760880faf5386dc01ca9867531631782941fedaa94521d09959edf71f046e393c7b91 +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 languageName: node linkType: hard @@ -20148,13 +20020,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^3.0.0": - version: 3.0.0 - resolution: "is-plain-obj@npm:3.0.0" - checksum: 10/a6ebdf8e12ab73f33530641972a72a4b8aed6df04f762070d823808303e4f76d87d5ea5bd76f96a7bbe83d93f04ac7764429c29413bd9049853a69cb630fb21c - languageName: node - linkType: hard - "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -20259,7 +20124,7 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.1": +"is-string@npm:^1.1.1": version: 1.1.1 resolution: "is-string@npm:1.1.1" dependencies: @@ -20298,7 +20163,7 @@ __metadata: languageName: node linkType: hard -"is-typedarray@npm:~1.0.0": +"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" checksum: 10/4b433bfb0f9026f079f4eb3fbaa4ed2de17c9995c3a0b5c800bec40799b4b2a8b4e051b1ada77749deb9ded4ae52fe2096973f3a93ff83df1a5a7184a669478c @@ -20349,12 +20214,12 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.0 - resolution: "is-weakref@npm:1.1.0" +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - checksum: 10/89e627cc1763ea110574bb408fcf060ede47e70437d9278858bc939e3b3f7e4b7c558610b733da5f2ad6084d9f12b9c714b011ccf3fa771ec87e221c22bed910 + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 languageName: node linkType: hard @@ -20375,7 +20240,14 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:^1.0.1": +"is-windows@npm:^0.2.0": + version: 0.2.0 + resolution: "is-windows@npm:0.2.0" + checksum: 10/3df25afda2fd9f3926b08cebacf1fc0a1fe7805a2cb73ef0f1b911c949e4e7648c4623979d74b4502bdd9af69471101eb6051b751595f7f88569148186cf7a7a + languageName: node + linkType: hard + +"is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 @@ -20391,15 +20263,6 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^3.1.0": - version: 3.1.0 - resolution: "is-wsl@npm:3.1.0" - dependencies: - is-inside-container: "npm:^1.0.0" - checksum: 10/f9734c81f2f9cf9877c5db8356bfe1ff61680f1f4c1011e91278a9c0564b395ae796addb4bf33956871041476ec82c3e5260ed57b22ac91794d4ae70a1d2f0a9 - languageName: node - linkType: hard - "is@npm:^3.3.0": version: 3.3.0 resolution: "is@npm:3.3.0" @@ -20477,6 +20340,27 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-hook@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-hook@npm:3.0.0" + dependencies: + append-transform: "npm:^2.0.0" + checksum: 10/512a996cce6b1b9003ba59eab42299dd1527176c01f3ceb7b16bf68f437eeab4958f9df7df0a6b258d45d5f1a2ca2a1bdb915970711e1a5d7b2de911c582f721 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^4.0.0": + version: 4.0.3 + resolution: "istanbul-lib-instrument@npm:4.0.3" + dependencies: + "@babel/core": "npm:^7.7.5" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.0.0" + semver: "npm:^6.3.0" + checksum: 10/6e04ab365b95644ec4954b645f901be90be8ad81233d6df536300cdafcf70dd1ed22a912ceda38b32053c7fc9830c44cd23550c603f493329a8532073d1d6c42 + languageName: node + linkType: hard + "istanbul-lib-instrument@npm:^5.0.4": version: 5.1.0 resolution: "istanbul-lib-instrument@npm:5.1.0" @@ -20503,6 +20387,20 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-processinfo@npm:^2.0.2": + version: 2.0.3 + resolution: "istanbul-lib-processinfo@npm:2.0.3" + dependencies: + archy: "npm:^1.0.0" + cross-spawn: "npm:^7.0.3" + istanbul-lib-coverage: "npm:^3.2.0" + p-map: "npm:^3.0.0" + rimraf: "npm:^3.0.0" + uuid: "npm:^8.3.2" + checksum: 10/60e7b3441687249460f34a817c7204967b07830a69b6e430e60a45615319c2ab4e2b2eaeb8b3decf549fccd419cd600d21173961632229967608d7d1b194f39e + languageName: node + linkType: hard + "istanbul-lib-report@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-report@npm:3.0.0" @@ -20525,13 +20423,13 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.1.3": - version: 3.1.5 - resolution: "istanbul-reports@npm:3.1.5" +"istanbul-reports@npm:^3.0.2, istanbul-reports@npm:^3.1.3": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" dependencies: html-escaper: "npm:^2.0.0" istanbul-lib-report: "npm:^3.0.0" - checksum: 10/1fc20a133f6dbd846e7bf3dc6d85edf2b3c047c47142cd796c38717aef976195d2c0fb0399dd609c3ffac2ca43244dc15ce4ac34064d21e2d34d387df747dafb + checksum: 10/f1faaa4684efaf57d64087776018d7426312a59aa6eeb4e0e3a777347d23cd286ad18f427e98f0e3dee666103d7404c9d7abc5f240406a912fa16bd6695437fa languageName: node linkType: hard @@ -20616,7 +20514,7 @@ __metadata: languageName: node linkType: hard -"jest-circus@npm:^29.7.0": +"jest-circus@npm:^29.6.4, jest-circus@npm:^29.7.0": version: 29.7.0 resolution: "jest-circus@npm:29.7.0" dependencies: @@ -20782,7 +20680,7 @@ __metadata: languageName: node linkType: hard -"jest-environment-node@npm:^29.7.0": +"jest-environment-node@npm:^29.6.4, jest-environment-node@npm:^29.7.0": version: 29.7.0 resolution: "jest-environment-node@npm:29.7.0" dependencies: @@ -20840,7 +20738,7 @@ __metadata: languageName: node linkType: hard -"jest-junit@npm:16.0.0": +"jest-junit@npm:16.0.0, jest-junit@npm:^16.0.0": version: 16.0.0 resolution: "jest-junit@npm:16.0.0" dependencies: @@ -20902,6 +20800,25 @@ __metadata: languageName: node linkType: hard +"jest-playwright-preset@npm:^4.0.0": + version: 4.0.0 + resolution: "jest-playwright-preset@npm:4.0.0" + dependencies: + expect-playwright: "npm:^0.8.0" + jest-process-manager: "npm:^0.4.0" + nyc: "npm:^15.1.0" + playwright-core: "npm:>=1.2.0" + rimraf: "npm:^3.0.2" + uuid: "npm:^8.3.2" + peerDependencies: + jest: ^29.3.1 + jest-circus: ^29.3.1 + jest-environment-node: ^29.3.1 + jest-runner: ^29.3.1 + checksum: 10/ad8b92b4eac452042d71666f6fa7ce56abd590ec12732ec7c08926ff48351f72ea07a3babb4843db9e2e5e0c3a01f0d22da6acb1eaadf9a43c84c368ba188127 + languageName: node + linkType: hard + "jest-pnp-resolver@npm:^1.2.2": version: 1.2.2 resolution: "jest-pnp-resolver@npm:1.2.2" @@ -20914,6 +20831,24 @@ __metadata: languageName: node linkType: hard +"jest-process-manager@npm:^0.4.0": + version: 0.4.0 + resolution: "jest-process-manager@npm:0.4.0" + dependencies: + "@types/wait-on": "npm:^5.2.0" + chalk: "npm:^4.1.0" + cwd: "npm:^0.10.0" + exit: "npm:^0.1.2" + find-process: "npm:^1.4.4" + prompts: "npm:^2.4.1" + signal-exit: "npm:^3.0.3" + spawnd: "npm:^5.0.0" + tree-kill: "npm:^1.2.2" + wait-on: "npm:^7.0.0" + checksum: 10/5d69465a497d1ba0afe7a8241c4819a6f88dfbc22355b89d8ca0a6a39fbe8c4f57dbfff5697f1875f05b117276261e2eb8cc3bbf3395e911646199dae46cdb31 + languageName: node + linkType: hard + "jest-regex-util@npm:30.0.1": version: 30.0.1 resolution: "jest-regex-util@npm:30.0.1" @@ -20955,7 +20890,7 @@ __metadata: languageName: node linkType: hard -"jest-runner@npm:^29.7.0": +"jest-runner@npm:^29.6.4, jest-runner@npm:^29.7.0": version: 29.7.0 resolution: "jest-runner@npm:29.7.0" dependencies: @@ -21014,6 +20949,15 @@ __metadata: languageName: node linkType: hard +"jest-serializer-html@npm:^7.1.0": + version: 7.1.0 + resolution: "jest-serializer-html@npm:7.1.0" + dependencies: + diffable-html: "npm:^4.1.0" + checksum: 10/64f9139a3ffb2971c8d03d41c543cb03633532dfa0d6a7d6017646556965a8549f7886452d942124181b24e2fecb9131a49c56f3a3ab1047bac86cbe24419894 + languageName: node + linkType: hard + "jest-snapshot@npm:^29.7.0": version: 29.7.0 resolution: "jest-snapshot@npm:29.7.0" @@ -21070,7 +21014,7 @@ __metadata: languageName: node linkType: hard -"jest-watch-typeahead@npm:^2.2.2": +"jest-watch-typeahead@npm:^2.0.0, jest-watch-typeahead@npm:^2.2.2": version: 2.2.2 resolution: "jest-watch-typeahead@npm:2.2.2" dependencies: @@ -21196,6 +21140,19 @@ __metadata: languageName: node linkType: hard +"joi@npm:^17.11.0": + version: 17.13.3 + resolution: "joi@npm:17.13.3" + dependencies: + "@hapi/hoek": "npm:^9.3.0" + "@hapi/topo": "npm:^5.1.0" + "@sideway/address": "npm:^4.1.5" + "@sideway/formula": "npm:^3.0.1" + "@sideway/pinpoint": "npm:^2.0.0" + checksum: 10/4c150db0c820c3a52f4a55c82c1fc5e144a5b5f4da9ffebc7339a15469d1a447ebb427ced446efcb9709ab56bd71a06c4c67c9381bc1b9f9ae63fc7c89209bdf + languageName: node + linkType: hard + "jpeg-js@npm:^0.4.4": version: 0.4.4 resolution: "jpeg-js@npm:0.4.4" @@ -21661,16 +21618,6 @@ __metadata: languageName: node linkType: hard -"launch-editor@npm:^2.6.1": - version: 2.6.1 - resolution: "launch-editor@npm:2.6.1" - dependencies: - picocolors: "npm:^1.0.0" - shell-quote: "npm:^1.8.1" - checksum: 10/e06d193075ac09f7f8109f10cabe464a211bf7ed4cbe75f83348d6f67bf4d9f162f06e7a1ab3e1cd7fc250b5342c3b57080618aff2e646dc34248fe499227601 - languageName: node - linkType: hard - "lazy-ass@npm:^1.6.0": version: 1.6.0 resolution: "lazy-ass@npm:1.6.0" @@ -22088,6 +22035,13 @@ __metadata: languageName: node linkType: hard +"lodash.flattendeep@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.flattendeep@npm:4.4.0" + checksum: 10/0d0b41d8d86999e8bea94905ac65347404d427aacddbc6654dc2f85905e27cd2b708139671ecea135fa6f0a17ed94b9d4cab8ce12b08eddcbb1ddd83952ee4c2 + languageName: node + linkType: hard + "lodash.get@npm:^4.4.2": version: 4.4.2 resolution: "lodash.get@npm:4.4.2" @@ -22237,6 +22191,13 @@ __metadata: languageName: node linkType: hard +"loglevel@npm:^1.9.2": + version: 1.9.2 + resolution: "loglevel@npm:1.9.2" + checksum: 10/6153d8db308323f7ee20130bc40309e7a976c30a10379d8666b596d9c6441965c3e074c8d7ee3347fe5cfc059c0375b6f3e8a10b93d5b813cc5547f5aa412a29 + languageName: node + linkType: hard + "long@npm:^5.0.0": version: 5.2.3 resolution: "long@npm:5.2.3" @@ -22532,12 +22493,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:16.0.0": - version: 16.0.0 - resolution: "marked@npm:16.0.0" +"marked@npm:16.1.1": + version: 16.1.1 + resolution: "marked@npm:16.1.1" bin: marked: bin/marked.js - checksum: 10/aed6501ff5d49def83f0c79fab698666338a555cae32f83ba52369c9a04aa8be4fa4c908367d12f81f7d1c1a83d89ffe47aa1e7b023da308591e03ea82d5ca6d + checksum: 10/9f4d2d8f862e1d874f03f1b8614fbf859ae788b4a1b4174be83a65c8ea5ecabf0c991c455dcb6b650e7106fb5b65a94865b9de45667bb2471ca2272e1798971d languageName: node linkType: hard @@ -22625,15 +22586,6 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.6.0": - version: 4.7.7 - resolution: "memfs@npm:4.7.7" - dependencies: - tslib: "npm:^2.0.0" - checksum: 10/311633e5857c91f41021b43f00eda8d540fed2c2d9e02c780fe78de720cfb55d15ab2d5b5ce9f2576637589b82e84488f1b9ff503563e817ed65200ad24617fb - languageName: node - linkType: hard - "memoize-one@npm:6.0.0, memoize-one@npm:^6.0.0": version: 6.0.0 resolution: "memoize-one@npm:6.0.0" @@ -22699,13 +22651,6 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:1.0.3": - version: 1.0.3 - resolution: "merge-descriptors@npm:1.0.3" - checksum: 10/52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 - languageName: node - linkType: hard - "merge-descriptors@npm:^2.0.0": version: 2.0.0 resolution: "merge-descriptors@npm:2.0.0" @@ -22727,13 +22672,6 @@ __metadata: languageName: node linkType: hard -"methods@npm:~1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 10/a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 - languageName: node - linkType: hard - "micro-memoize@npm:^4.1.2": version: 4.1.3 resolution: "micro-memoize@npm:4.1.3" @@ -22758,14 +22696,14 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": +"mime-db@npm:^1.54.0": version: 1.54.0 resolution: "mime-db@npm:1.54.0" checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -22852,13 +22790,6 @@ __metadata: languageName: node linkType: hard -"minimalistic-assert@npm:^1.0.0": - version: 1.0.1 - resolution: "minimalistic-assert@npm:1.0.1" - checksum: 10/cc7974a9268fbf130fb055aff76700d7e2d8be5f761fb5c60318d0ed010d839ab3661a533ad29a5d37653133385204c503bfac995aaa4236f4e847461ea32ba7 - languageName: node - linkType: hard - "minimatch@npm:3.0.5": version: 3.0.5 resolution: "minimatch@npm:3.0.5" @@ -23222,10 +23153,10 @@ __metadata: languageName: node linkType: hard -"monaco-promql@npm:1.7.4": - version: 1.7.4 - resolution: "monaco-promql@npm:1.7.4" - checksum: 10/5bafbc3c1f00da27fd637ebc5ff106e96951ebf688b99b8ad7c447fee1d3298c3873369914c599aec836fa019ea3b8b93ce3b02157d0666d394b06b8a55e728c +"monaco-promql@npm:1.8.0": + version: 1.8.0 + resolution: "monaco-promql@npm:1.8.0" + checksum: 10/d757ecd46fb3fb3ee31d1ee06b21e2860735d24d41ca161e8d4e576a956a83e86103b7ddbecdf1e02ea9fac6a3782694c996e3ce46b7483f3c47630627b40c68 languageName: node linkType: hard @@ -23323,18 +23254,6 @@ __metadata: languageName: node linkType: hard -"multicast-dns@npm:^7.2.5": - version: 7.2.5 - resolution: "multicast-dns@npm:7.2.5" - dependencies: - dns-packet: "npm:^5.2.2" - thunky: "npm:^1.0.2" - bin: - multicast-dns: cli.js - checksum: 10/e9add8035fb7049ccbc87b1b069f05bb3b31e04fe057bf7d0116739d81295165afc2568291a4a962bee01a5074e475996816eed0f50c8110d652af5abb74f95a - languageName: node - linkType: hard - "multimatch@npm:5.0.0": version: 5.0.0 resolution: "multimatch@npm:5.0.0" @@ -23590,7 +23509,7 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1, node-forge@npm:^1.3.1": +"node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d @@ -23697,6 +23616,15 @@ __metadata: languageName: node linkType: hard +"node-preload@npm:^0.2.1": + version: 0.2.1 + resolution: "node-preload@npm:0.2.1" + dependencies: + process-on-spawn: "npm:^1.0.0" + checksum: 10/de36ed365b7e474eaf05c41f976774dece23a7f398fe76dbf9705f9670a1f49e6a27c5f31fe58b4e43d96413fdce4806192c60d35317b25725636c90889d5bab + languageName: node + linkType: hard + "node-readfiles@npm:^0.2.0": version: 0.2.0 resolution: "node-readfiles@npm:0.2.0" @@ -24012,6 +23940,43 @@ __metadata: languageName: node linkType: hard +"nyc@npm:^15.1.0": + version: 15.1.0 + resolution: "nyc@npm:15.1.0" + dependencies: + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + caching-transform: "npm:^4.0.0" + convert-source-map: "npm:^1.7.0" + decamelize: "npm:^1.2.0" + find-cache-dir: "npm:^3.2.0" + find-up: "npm:^4.1.0" + foreground-child: "npm:^2.0.0" + get-package-type: "npm:^0.1.0" + glob: "npm:^7.1.6" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-hook: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^4.0.0" + istanbul-lib-processinfo: "npm:^2.0.2" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.0.2" + make-dir: "npm:^3.0.0" + node-preload: "npm:^0.2.1" + p-map: "npm:^3.0.0" + process-on-spawn: "npm:^1.0.0" + resolve-from: "npm:^5.0.0" + rimraf: "npm:^3.0.0" + signal-exit: "npm:^3.0.2" + spawn-wrap: "npm:^2.0.0" + test-exclude: "npm:^6.0.0" + yargs: "npm:^15.0.2" + bin: + nyc: bin/nyc.js + checksum: 10/c987f04f4192dfd94e9e69869c76a54220b3ed555016751f380a413a378cceff8ec346df579e9126035b6acbc60ab893cc65e67729cc427c0171361bcb481e66 + languageName: node + linkType: hard + "oas-kit-common@npm:^1.0.8": version: 1.0.8 resolution: "oas-kit-common@npm:1.0.8" @@ -24102,10 +24067,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": - version: 1.13.3 - resolution: "object-inspect@npm:1.13.3" - checksum: 10/14cb973d8381c69e14d7f1c8c75044eb4caf04c6dabcf40ca5c2ce42dc2073ae0bb2a9939eeca142b0c05215afaa1cd5534adb7c8879c32cba2576e045ed8368 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard @@ -24215,7 +24180,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" dependencies: @@ -24234,13 +24199,6 @@ __metadata: languageName: node linkType: hard -"obuf@npm:^1.0.0, obuf@npm:^1.1.2": - version: 1.1.2 - resolution: "obuf@npm:1.1.2" - checksum: 10/53ff4ab3a13cc33ba6c856cf281f2965c0aec9720967af450e8fd06cfd50aceeefc791986a16bcefa14e7898b3ca9acdfcf15b9d9a1b9c7e1366581a8ad6e65e - languageName: node - linkType: hard - "ol-ext@npm:4.0.33": version: 4.0.33 resolution: "ol-ext@npm:4.0.33" @@ -24250,16 +24208,16 @@ __metadata: languageName: node linkType: hard -"ol@npm:10.6.0": - version: 10.6.0 - resolution: "ol@npm:10.6.0" +"ol@npm:10.6.1": + version: 10.6.1 + resolution: "ol@npm:10.6.1" dependencies: "@types/rbush": "npm:4.0.0" earcut: "npm:^3.0.0" geotiff: "npm:^2.1.3" pbf: "npm:4.0.1" rbush: "npm:^4.0.0" - checksum: 10/942fe6c0b55b24a6b1538d78ad221962476bfcba5b1316f427235d50aa817ca5ed805e8d3b5c6129b5da245ba0b66cd8bf359403230bb939648a1a78bffb0ca3 + checksum: 10/f112a2907ff8644b19cf29fcabdf34a4c124828f6efb9687d27a12ad5b76588cfda436b9100147d29875eaa1e4543d788514db35ecdbaaa7d19e4c4bd9e183a8 languageName: node linkType: hard @@ -24288,13 +24246,6 @@ __metadata: languageName: node linkType: hard -"on-headers@npm:~1.0.2": - version: 1.0.2 - resolution: "on-headers@npm:1.0.2" - checksum: 10/870766c16345855e2012e9422ba1ab110c7e44ad5891a67790f84610bd70a72b67fdd71baf497295f1d1bf38dd4c92248f825d48729c53c0eae5262fb69fa171 - languageName: node - linkType: hard - "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -24322,18 +24273,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^10.0.3": - version: 10.0.3 - resolution: "open@npm:10.0.3" - dependencies: - default-browser: "npm:^5.2.1" - define-lazy-prop: "npm:^3.0.0" - is-inside-container: "npm:^1.0.0" - is-wsl: "npm:^3.1.0" - checksum: 10/4dc757ad1d3d63490822f991e9cbe3a7c05b7249fca2eaa571cb7d191e5cec88bc37e15d8ef4fd740d8989a288b661d8da253caa8d98e8c97430ddbbb0ae4ed1 - languageName: node - linkType: hard - "open@npm:^8.0.4, open@npm:^8.4.0, open@npm:^8.4.2": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -24443,6 +24382,13 @@ __metadata: languageName: node linkType: hard +"os-homedir@npm:^1.0.1": + version: 1.0.2 + resolution: "os-homedir@npm:1.0.2" + checksum: 10/af609f5a7ab72de2f6ca9be6d6b91a599777afc122ac5cad47e126c1f67c176fe9b52516b9eeca1ff6ca0ab8587fe66208bc85e40a3940125f03cdb91408e9d2 + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -24579,6 +24525,15 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^3.0.0": + version: 3.0.0 + resolution: "p-map@npm:3.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10/d4a0664d2af05d7e5f6f342e6493d4cad48f7398ac803c5066afb1f8d2010bfc2a83d935689437288f7b1a743772085b8fa0909a8282b5df4210bcda496c37c8 + languageName: node + linkType: hard + "p-map@npm:^5.5.0": version: 5.5.0 resolution: "p-map@npm:5.5.0" @@ -24612,17 +24567,6 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:^6.2.0": - version: 6.2.0 - resolution: "p-retry@npm:6.2.0" - dependencies: - "@types/retry": "npm:0.12.2" - is-network-error: "npm:^1.0.0" - retry: "npm:^0.13.1" - checksum: 10/1a5ac16828c96c03c354f78d643dfc7aa8f8b998e1b60e27533da2c75e5cabfb1c7f88ce312e813e09a80b056011fbb372d384132e9c92d27d052bd7c282a978 - languageName: node - linkType: hard - "p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" @@ -24704,6 +24648,18 @@ __metadata: languageName: node linkType: hard +"package-hash@npm:^4.0.0": + version: 4.0.0 + resolution: "package-hash@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.15" + hasha: "npm:^5.0.0" + lodash.flattendeep: "npm:^4.4.0" + release-zalgo: "npm:^1.0.0" + checksum: 10/c7209d98ac31926e0c1753d014f8b6b924e1e6a1aacf833dc99edece9c8381424c41c97c26c7eee82026944a79e99023cde5998bf515d7465c87005d52152040 + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.0 resolution: "package-json-from-dist@npm:1.0.0" @@ -24979,7 +24935,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 @@ -25098,13 +25054,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.12": - version: 0.1.12 - resolution: "path-to-regexp@npm:0.1.12" - checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e - languageName: node - linkType: hard - "path-to-regexp@npm:^1.7.0": version: 1.9.0 resolution: "path-to-regexp@npm:1.9.0" @@ -25322,27 +25271,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.52.0": - version: 1.52.0 - resolution: "playwright-core@npm:1.52.0" +"playwright-core@npm:1.54.1, playwright-core@npm:>=1.2.0": + version: 1.54.1 + resolution: "playwright-core@npm:1.54.1" bin: playwright-core: cli.js - checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c + checksum: 10/c0acfb7ecb48e9fb6c22f71298b966244bd4f8659093a798cc7f9a509deee22109560e44f2d507124e7718779640e0b4cb05a05c068b034405418d8740ec31ff languageName: node linkType: hard -"playwright@npm:1.52.0": - version: 1.52.0 - resolution: "playwright@npm:1.52.0" +"playwright@npm:1.54.1, playwright@npm:^1.14.0": + version: 1.54.1 + resolution: "playwright@npm:1.54.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.52.0" + playwright-core: "npm:1.54.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec + checksum: 10/ebad2924b589a4cfedf48288e67b4afc81047222e607321c9452a37610025e6e249bdfeff010c1c70cf9a9d35c0b9e5cb7934a58986f67928dd469d9fde1a035 languageName: node linkType: hard @@ -25886,12 +25835,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.4.2, prettier@npm:^3.1.1, prettier@npm:^3.2.5": - version: 3.4.2 - resolution: "prettier@npm:3.4.2" +"prettier@npm:3.6.2, prettier@npm:^3.1.1, prettier@npm:^3.2.5": + version: 3.6.2 + resolution: "prettier@npm:3.6.2" bin: prettier: bin/prettier.cjs - checksum: 10/a3e806fb0b635818964d472d35d27e21a4e17150c679047f5501e1f23bd4aa806adf660f0c0d35214a210d5d440da6896c2e86156da55f221a57938278dc326e + checksum: 10/1213691706bcef1371d16ef72773c8111106c3533b660b1cc8ec158bd109cdf1462804125f87f981f23c4a3dba053b6efafda30ab0114cc5b4a725606bb9ff26 languageName: node linkType: hard @@ -25978,6 +25927,15 @@ __metadata: languageName: node linkType: hard +"process-on-spawn@npm:^1.0.0": + version: 1.1.0 + resolution: "process-on-spawn@npm:1.1.0" + dependencies: + fromentries: "npm:^1.2.0" + checksum: 10/4cc56df51bf54d7629c1857e472c9440984d230c4a4dfdfc2de25abcee57b3d8f4bdfb0b9ad65fe7eea11a7a10f03474c3e8c5eb554454d32c86444e635c85f8 + languageName: node + linkType: hard + "process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" @@ -26037,7 +25995,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1": +"prompts@npm:^2.0.1, prompts@npm:^2.4.1": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -26120,7 +26078,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": +"proxy-addr@npm:^2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -26884,11 +26842,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.49.2": - version: 7.60.0 - resolution: "react-hook-form@npm:7.60.0" + version: 7.61.1 + resolution: "react-hook-form@npm:7.61.1" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/cc86ef4029f7b06f82dff919c8933156683b70669a6306d2471706443192b8916afaa07bc64452b590ecb18ea94f3d156f3fb38d59ada3462f5b108e89e186c9 + checksum: 10/961c5ad1f6fe694a66c1aa9934e5cd7ef014a8fd0d4ed15ac6d97d31d8d53b9e6196cb8b66d119883f49508c11589bc4b1478e1080790eb2e91c476551c82186 languageName: node linkType: hard @@ -27567,7 +27525,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.6, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.6, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -27582,7 +27540,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -27743,7 +27701,7 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:0.14.1, regenerator-runtime@npm:^0.14.0": +"regenerator-runtime@npm:0.14.1": version: 0.14.1 resolution: "regenerator-runtime@npm:0.14.1" checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 @@ -27757,15 +27715,6 @@ __metadata: languageName: node linkType: hard -"regenerator-transform@npm:^0.15.2": - version: 0.15.2 - resolution: "regenerator-transform@npm:0.15.2" - dependencies: - "@babel/runtime": "npm:^7.8.4" - checksum: 10/c4fdcb46d11bbe32605b4b9ed76b21b8d3f241a45153e9dc6f5542fed4c7744fed459f42701f650d5d5956786bf7de57547329d1c05a9df2ed9e367b9d903302 - languageName: node - linkType: hard - "regexp-tree@npm:^0.1.27": version: 0.1.27 resolution: "regexp-tree@npm:0.1.27" @@ -27775,7 +27724,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -27789,17 +27738,17 @@ __metadata: languageName: node linkType: hard -"regexpu-core@npm:^6.1.1": - version: 6.1.1 - resolution: "regexpu-core@npm:6.1.1" +"regexpu-core@npm:^6.2.0": + version: 6.2.0 + resolution: "regexpu-core@npm:6.2.0" dependencies: regenerate: "npm:^1.4.2" regenerate-unicode-properties: "npm:^10.2.0" regjsgen: "npm:^0.8.0" - regjsparser: "npm:^0.11.0" + regjsparser: "npm:^0.12.0" unicode-match-property-ecmascript: "npm:^2.0.0" unicode-match-property-value-ecmascript: "npm:^2.1.0" - checksum: 10/6a7ffb42781cacedd7df3c47c72e2d725401a699855be94a37ece5e29d3f25ab3abdd81d73f2d9d32ebc4d41bd25e3c3cc21e5284203faf19e60943adc55252d + checksum: 10/4d054ffcd98ca4f6ca7bf0df6598ed5e4a124264602553308add41d4fa714a0c5bcfb5bc868ac91f7060a9c09889cc21d3180a3a14c5f9c5838442806129ced3 languageName: node linkType: hard @@ -27821,14 +27770,14 @@ __metadata: languageName: node linkType: hard -"regjsparser@npm:^0.11.0": - version: 0.11.0 - resolution: "regjsparser@npm:0.11.0" +"regjsparser@npm:^0.12.0": + version: 0.12.0 + resolution: "regjsparser@npm:0.12.0" dependencies: jsesc: "npm:~3.0.2" bin: regjsparser: bin/parser - checksum: 10/d8963f9bbb4da0795faaea11130abdb15c17565d515f15c990a2433912e5ed80077cbd7ad99530bb1fc8471c82c321a0699c5f48a2ce00715a7151b027fced55 + checksum: 10/c2d6506b3308679de5223a8916984198e0493649a67b477c66bdb875357e3785abbf3bedf7c5c2cf8967d3b3a7bdf08b7cbd39e65a70f9e1ffad584aecf5f06a languageName: node linkType: hard @@ -27839,6 +27788,15 @@ __metadata: languageName: node linkType: hard +"release-zalgo@npm:^1.0.0": + version: 1.0.0 + resolution: "release-zalgo@npm:1.0.0" + dependencies: + es6-error: "npm:^4.0.1" + checksum: 10/1719e44b240ee1f57d034b26ea167f3cbf3c36fdae6d6efd0e6e5b202d9852baffc1c5595d378b5f8b2ad729b907ddd962f3d051d89499f83584993a5399f964 + languageName: node + linkType: hard + "reliable-random@npm:^0.0.1": version: 0.0.1 resolution: "reliable-random@npm:0.0.1" @@ -27933,6 +27891,13 @@ __metadata: languageName: node linkType: hard +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10/8604a570c06a69c9d939275becc33a65676529e1c3e5a9f42d58471674df79357872b96d70bb93a0380a62d60dc9031c98b1a9dad98c946ffdd61b7ac0c8cedd + languageName: node + linkType: hard + "requireindex@npm:^1.2.0": version: 1.2.0 resolution: "requireindex@npm:1.2.0" @@ -27970,6 +27935,16 @@ __metadata: languageName: node linkType: hard +"resolve-dir@npm:^0.1.0": + version: 0.1.1 + resolution: "resolve-dir@npm:0.1.1" + dependencies: + expand-tilde: "npm:^1.2.2" + global-modules: "npm:^0.2.3" + checksum: 10/cc3e1885938f8fe9656a6faa651e21730d369260e907b8dd7c847a4aa18db348ac08ee0dbf2d6f87e2ba08715fb109432ec773bbb31698381bd2a48c0ea66072 + languageName: node + linkType: hard + "resolve-dir@npm:^1.0.0, resolve-dir@npm:^1.0.1": version: 1.0.1 resolution: "resolve-dir@npm:1.0.1" @@ -28033,16 +28008,16 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" +"resolve@npm:^1.10.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" dependencies: - is-core-module: "npm:^2.13.0" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/c473506ee01eb45cbcfefb68652ae5759e092e6b0fb64547feadf9736a6394f258fbc6f88e00c5ca36d5477fbb65388b272432a3600fa223062e54333c156753 + checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181 languageName: node linkType: hard @@ -28059,16 +28034,16 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: - is-core-module: "npm:^2.13.0" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/f345cd37f56a2c0275e3fe062517c650bb673815d885e7507566df589375d165bbbf4bdb6aa95600a9bc55f4744b81f452b5a63f95b9f10a72787dba3c90890a + checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a languageName: node linkType: hard @@ -28119,13 +28094,6 @@ __metadata: languageName: node linkType: hard -"retry@npm:^0.13.1": - version: 0.13.1 - resolution: "retry@npm:0.13.1" - checksum: 10/6125ec2e06d6e47e9201539c887defba4e47f63471db304c59e4b82fc63c8e89ca06a77e9d34939a9a42a76f00774b2f46c0d4a4cbb3e287268bd018ed69426d - languageName: node - linkType: hard - "reusify@npm:^1.0.4": version: 1.0.4 resolution: "reusify@npm:1.0.4" @@ -28163,7 +28131,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -28205,27 +28173,27 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-esbuild@npm:6.2.0": - version: 6.2.0 - resolution: "rollup-plugin-esbuild@npm:6.2.0" +"rollup-plugin-esbuild@npm:6.2.1": + version: 6.2.1 + resolution: "rollup-plugin-esbuild@npm:6.2.1" dependencies: debug: "npm:^4.4.0" es-module-lexer: "npm:^1.6.0" get-tsconfig: "npm:^4.10.0" - unplugin-utils: "npm:^0.2.3" + unplugin-utils: "npm:^0.2.4" peerDependencies: esbuild: ">=0.18.0" rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - checksum: 10/305563dfa72a843fd7db7b885583f22f62d4d584cbce7e8d80f138692ce67eb34e45dc1227b3420662f22fa75fa603ca09b64e7d1670919275a8577da653fada + checksum: 10/e5731b86c4e01c6ccd3998a2f90794bd738fe83a3a809b9e82456e4b1d173c296ef302c2a6669b966b12c757c1787f0585ae88cd73b98f3c0ad6ad7f779940aa languageName: node linkType: hard "rollup-plugin-node-externals@npm:^8.0.0": - version: 8.0.0 - resolution: "rollup-plugin-node-externals@npm:8.0.0" + version: 8.0.1 + resolution: "rollup-plugin-node-externals@npm:8.0.1" peerDependencies: rollup: ^4.0.0 - checksum: 10/ef5565e8fdba4acbba81c4b214f789e03d3a3d049cbbe372679151c4f749a21a7f51d8448eff82b0b1bbbe5d2d64862cd292795e0f92e1fcad2afc9954a6fbdd + checksum: 10/576fa3cd16a3cbbeffe6ea84833c820117d76b62e5625ed5c94ebb769dea1c763093c7f3ec62b49f77a2487f9b5988283d97e54ecd6750915ab754dfe0ae0e25 languageName: node linkType: hard @@ -28368,13 +28336,6 @@ __metadata: languageName: node linkType: hard -"run-applescript@npm:^7.0.0": - version: 7.0.0 - resolution: "run-applescript@npm:7.0.0" - checksum: 10/b02462454d8b182ad4117e5d4626e9e6782eb2072925c9fac582170b0627ae3c1ea92ee9b2df7daf84b5e9ffe14eb1cf5fb70bc44b15c8a0bfcdb47987e2410c - languageName: node - linkType: hard - "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -28494,9 +28455,9 @@ __metadata: languageName: node linkType: hard -"sass-loader@npm:16.0.4": - version: 16.0.4 - resolution: "sass-loader@npm:16.0.4" +"sass-loader@npm:16.0.5": + version: 16.0.5 + resolution: "sass-loader@npm:16.0.5" dependencies: neo-async: "npm:^2.6.2" peerDependencies: @@ -28516,7 +28477,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/16e3552e04301864d59d99f1c90952e0c97cfa793a38a551b6dc19e49a9c0779ec8b3dd8ab39bc79cc4401972f13e8d5baaf577c96ea39911db5866a0b4bdcdd + checksum: 10/978b553900427c3fc24ed16b8258829d6a851bc5b0ab226cf43143fc08a0386e8cd07db367104d190a6cf0945af071805f70773525a970673c5b61fda4b7a59e languageName: node linkType: hard @@ -28615,13 +28576,6 @@ __metadata: languageName: node linkType: hard -"select-hose@npm:^2.0.0": - version: 2.0.0 - resolution: "select-hose@npm:2.0.0" - checksum: 10/08cdd629a394d20e9005e7956f0624307c702cf950cc0458953e9b87ea961d3b1b72ac02266bdb93ac1eec4fcf42b41db9cabe93aa2b7683d71513d133c44fb5 - languageName: node - linkType: hard - "selection-is-backward@npm:^1.0.0": version: 1.0.0 resolution: "selection-is-backward@npm:1.0.0" @@ -28647,16 +28601,6 @@ __metadata: languageName: node linkType: hard -"selfsigned@npm:^2.4.1": - version: 2.4.1 - resolution: "selfsigned@npm:2.4.1" - dependencies: - "@types/node-forge": "npm:^1.3.0" - node-forge: "npm:^1" - checksum: 10/52536623f1cfdeb2f8b9198377f2ce7931c677ea69421238d1dc1ea2983bbe258e56c19e7d1af87035cad7270f19b7e996eaab1212e724d887722502f68e17f2 - languageName: node - linkType: hard - "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -28684,17 +28628,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:~7.3.5": - version: 7.3.8 - resolution: "semver@npm:7.3.8" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 10/c8c04a4d41d30cffa7277904e0ad6998623dd61e36bca9578b0128d8c683b705a3924beada55eae7fa004fb30a9359a53a4ead2b68468d778b602f3b1a28f8e3 - languageName: node - linkType: hard - "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -28764,21 +28697,6 @@ __metadata: languageName: node linkType: hard -"serve-index@npm:^1.9.1": - version: 1.9.1 - resolution: "serve-index@npm:1.9.1" - dependencies: - accepts: "npm:~1.3.4" - batch: "npm:0.6.1" - debug: "npm:2.6.9" - escape-html: "npm:~1.0.3" - http-errors: "npm:~1.6.2" - mime-types: "npm:~2.1.17" - parseurl: "npm:~1.3.2" - checksum: 10/2adce2878d7e30f197e66f30e39f4a404d9ae39295c0c13849bb25e7cf976b93e883204739efd1510559588bed56f8101e32191cbe75f374c6e1e803852194cb - languageName: node - linkType: hard - "serve-static@npm:1.16.2": version: 1.16.2 resolution: "serve-static@npm:1.16.2" @@ -28854,13 +28772,6 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.1.0": - version: 1.1.0 - resolution: "setprototypeof@npm:1.1.0" - checksum: 10/02d2564e02a260551bab3ec95358dcfde775fe61272b1b7c488de3676a4bb79f280b5668a324aebe0ec73f0d8ba408bc2d816a609ee5d93b1a7936b9d4ba1208 - languageName: node - linkType: hard - "setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -28905,13 +28816,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.8.1": - version: 1.8.1 - resolution: "shell-quote@npm:1.8.1" - checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d - languageName: node - linkType: hard - "shellwords@npm:^0.1.1": version: 0.1.1 resolution: "shellwords@npm:0.1.1" @@ -29356,17 +29260,6 @@ __metadata: languageName: node linkType: hard -"sockjs@npm:^0.3.24": - version: 0.3.24 - resolution: "sockjs@npm:0.3.24" - dependencies: - faye-websocket: "npm:^0.11.3" - uuid: "npm:^8.3.2" - websocket-driver: "npm:^0.7.4" - checksum: 10/36312ec9772a0e536b69b72e9d1c76bd3d6ecf885c5d8fd6e59811485c916b8ce75f46ec57532f436975815ee14aa9a0e22ae3d9e5c0b18ea37b56d0aaaf439c - languageName: node - linkType: hard - "socks-proxy-agent@npm:^6.0.0": version: 6.1.0 resolution: "socks-proxy-agent@npm:6.1.0" @@ -29496,6 +29389,32 @@ __metadata: languageName: node linkType: hard +"spawn-wrap@npm:^2.0.0": + version: 2.0.0 + resolution: "spawn-wrap@npm:2.0.0" + dependencies: + foreground-child: "npm:^2.0.0" + is-windows: "npm:^1.0.2" + make-dir: "npm:^3.0.0" + rimraf: "npm:^3.0.0" + signal-exit: "npm:^3.0.2" + which: "npm:^2.0.1" + checksum: 10/ce6ca08d66c3a41a28a7ecc10bf4945d7930fd3ae961d40804ee109cee6ee9f8436125f53bc07918ca1eb461fe2ff0033af1dc3cb803469b585639675fc2d2e7 + languageName: node + linkType: hard + +"spawnd@npm:^5.0.0": + version: 5.0.0 + resolution: "spawnd@npm:5.0.0" + dependencies: + exit: "npm:^0.1.2" + signal-exit: "npm:^3.0.3" + tree-kill: "npm:^1.2.2" + wait-port: "npm:^0.2.9" + checksum: 10/6b1a8e4096efbf77729818ba09b9a54a3f6f9c002dfd62610f6d35ea80b21bd4a9c39a3d86650440ccd364f6bc2f46da1fa292836fbb1e9af02ca64a58990184 + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.1.1 resolution: "spdx-correct@npm:3.1.1" @@ -29540,33 +29459,6 @@ __metadata: languageName: node linkType: hard -"spdy-transport@npm:^3.0.0": - version: 3.0.0 - resolution: "spdy-transport@npm:3.0.0" - dependencies: - debug: "npm:^4.1.0" - detect-node: "npm:^2.0.4" - hpack.js: "npm:^2.1.6" - obuf: "npm:^1.1.2" - readable-stream: "npm:^3.0.6" - wbuf: "npm:^1.7.3" - checksum: 10/b93b606b209ca785456bd850b8925f21a76522ee5b46701235ecff3eba17686560c27575f91863842dc843a39772f6d2f5a8755df9eaff0924d20598df18828d - languageName: node - linkType: hard - -"spdy@npm:^4.0.2": - version: 4.0.2 - resolution: "spdy@npm:4.0.2" - dependencies: - debug: "npm:^4.1.0" - handle-thing: "npm:^2.0.0" - http-deceiver: "npm:^1.2.7" - select-hose: "npm:^2.0.0" - spdy-transport: "npm:^3.0.0" - checksum: 10/d29b89e48e7d762e505a2f83b1bc2c92268bd518f1b411864ab42a9e032e387d10467bbce0d8dbf8647bf4914a063aa1d303dff85e248f7a57f81a7b18ac34ef - languageName: node - linkType: hard - "spel2js@npm:^0.2.8": version: 0.2.8 resolution: "spel2js@npm:0.2.8" @@ -29740,7 +29632,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:~1.5.0": +"statuses@npm:~1.5.0": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c @@ -29761,6 +29653,16 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10/ff36c4db171ee76c936ccfe9541946b77017f12703d4c446652017356816862d3aa029a64e7d4c4ceb484e00ed4a81789333896390d808458638f3a216aa1f41 + languageName: node + linkType: hard + "storybook@npm:^8.6.2": version: 8.6.2 resolution: "storybook@npm:8.6.2" @@ -29950,7 +29852,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": +"string.prototype.trimend@npm:^1.0.9": version: 1.0.9 resolution: "string.prototype.trimend@npm:1.0.9" dependencies: @@ -30509,9 +30411,9 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:5.3.11, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10": - version: 5.3.11 - resolution: "terser-webpack-plugin@npm:5.3.11" +"terser-webpack-plugin@npm:5.3.14, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10": + version: 5.3.14 + resolution: "terser-webpack-plugin@npm:5.3.14" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" @@ -30527,7 +30429,7 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/a8f7c92c75aa42628adfa4d171d4695c366c1852ecb4a24e72dd6fec86e383e12ac24b627e798fedff4e213c21fe851cebc61be3ab5a2537e6e42bea46690aa3 + checksum: 10/5b7290f7edb179b83cefb8827c12371ddddc088cf251cf58a1c738d82628331ae6604273b61fe991d77411d4bb6b7178c3826aa47edf01b4ee21f973d6c8b8fb languageName: node linkType: hard @@ -30628,13 +30530,6 @@ __metadata: languageName: node linkType: hard -"thunky@npm:^1.0.2": - version: 1.1.0 - resolution: "thunky@npm:1.1.0" - checksum: 10/825e3bd07ab3c9fd6f753c457a60957c628cacba5dd0656fd93b037c445e2828b43cf0805a9f2b16b0c5f5a10fd561206271acddb568df4f867f0aea0eb2772f - languageName: node - linkType: hard - "tiny-invariant@npm:^1.0.1, tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.2.0, tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -30878,7 +30773,7 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:1.2.2": +"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" bin: @@ -31230,7 +31125,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.8.1": +"type-fest@npm:^0.8.0, type-fest@npm:^0.8.1": version: 0.8.1 resolution: "type-fest@npm:0.8.1" checksum: 10/fd4a91bfb706aeeb0d326ebd2e9a8ea5263979e5dec8d16c3e469a5bd3a946e014a062ef76c02e3086d3d1c7209a56a20a4caafd0e9f9a5c2ab975084ea3d388 @@ -31332,6 +31227,15 @@ __metadata: languageName: node linkType: hard +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: "npm:^1.0.0" + checksum: 10/7c850c3433fbdf4d04f04edfc751743b8f577828b8e1eb93b95a3bce782d156e267d83e20fb32b3b47813e69a69ab5e9b5342653332f7d21c7d1210661a7a72c + languageName: node + linkType: hard + "typedarray@npm:^0.0.6": version: 0.0.6 resolution: "typedarray@npm:0.0.6" @@ -31636,13 +31540,13 @@ __metadata: languageName: node linkType: hard -"unplugin-utils@npm:^0.2.3": - version: 0.2.3 - resolution: "unplugin-utils@npm:0.2.3" +"unplugin-utils@npm:^0.2.4": + version: 0.2.4 + resolution: "unplugin-utils@npm:0.2.4" dependencies: pathe: "npm:^2.0.2" picomatch: "npm:^4.0.2" - checksum: 10/95a18001b4a3aa92e58259162ee70be74f49780bff7aca0272035b206143772d07f3b117922ec9d7caaed8bd24578afcf7baa4330943796ea9f011dcc96af1d9 + checksum: 10/9cb0f9b364e57d5939f1bc75cdebca0637b0aa177847970c1c1032770e06013fed730bf94168709eb406092812f516e8bfee4aef0cfebc085520d95db1c7f529 languageName: node linkType: hard @@ -31926,7 +31830,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 @@ -32003,27 +31907,27 @@ __metadata: languageName: node linkType: hard -"vis-data@npm:^7.1.10": - version: 7.1.10 - resolution: "vis-data@npm:7.1.10" +"vis-data@npm:^8.0.0": + version: 8.0.1 + resolution: "vis-data@npm:8.0.1" peerDependencies: uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - vis-util: ^5.0.1 - checksum: 10/23fb2ef26864153013372e1d95107765be86dd9ce96f987bf99fdd93759fbe5ec1bd2603d354ca18a03f0fb607b829396ec02fe005aead63ef24599512f21402 + vis-util: ">=6.0.0" + checksum: 10/d69045c555a79fde292270d42f111b0d1ba644989481eec08393f73cf64573563e6533718eff0534f4255f44d363b078d0520704190328b97f8e244d4b4c8b73 languageName: node linkType: hard -"vis-network@npm:9.1.13": - version: 9.1.13 - resolution: "vis-network@npm:9.1.13" +"vis-network@npm:10.0.1": + version: 10.0.1 + resolution: "vis-network@npm:10.0.1" peerDependencies: "@egjs/hammerjs": ^2.0.0 component-emitter: ^1.3.0 || ^2.0.0 keycharm: ^0.2.0 || ^0.3.0 || ^0.4.0 uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - vis-data: ^6.3.0 || ^7.0.0 - vis-util: ^5.0.1 - checksum: 10/7fd7264ee0a79282596efece988a78c037c418fa5e79aba632521a3093e9742a438b254dc698f488eb7707e3f7033352c25e7651a8d49b42662af0b368a12db7 + vis-data: ">=8.0.0" + vis-util: ">=6.0.0" + checksum: 10/a44310daa9c4e361ccfceecc681791938dffbb292673de9438a640a10cf8f79674f7b33e77fbc747d482ce63177478eed83a30ac3ba4f8a55e01895b69554664 languageName: node linkType: hard @@ -32050,6 +31954,34 @@ __metadata: languageName: node linkType: hard +"wait-on@npm:^7.0.0": + version: 7.2.0 + resolution: "wait-on@npm:7.2.0" + dependencies: + axios: "npm:^1.6.1" + joi: "npm:^17.11.0" + lodash: "npm:^4.17.21" + minimist: "npm:^1.2.8" + rxjs: "npm:^7.8.1" + bin: + wait-on: bin/wait-on + checksum: 10/00299e3b651c70d7082d02b93d9d4784cbe851914f1674d795d578d4826876193fdc7bee7e9491264b7c2d242ac9fe6e1fd09e1143409f730f13a7ee2da67fff + languageName: node + linkType: hard + +"wait-port@npm:^0.2.9": + version: 0.2.14 + resolution: "wait-port@npm:0.2.14" + dependencies: + chalk: "npm:^2.4.2" + commander: "npm:^3.0.2" + debug: "npm:^4.1.1" + bin: + wait-port: bin/wait-port.js + checksum: 10/3a970828f6b9080d05bbe39ea6c0546ab7b455fec65b63fc704cdcbccb4d83c66815ef3bc2e0e123aab88a2afb70532c5697af27284b36bd03cb06cd446066a8 + languageName: node + linkType: hard + "walk-sync@npm:^2.2.0": version: 2.2.0 resolution: "walk-sync@npm:2.2.0" @@ -32097,15 +32029,6 @@ __metadata: languageName: node linkType: hard -"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": - version: 1.7.3 - resolution: "wbuf@npm:1.7.3" - dependencies: - minimalistic-assert: "npm:^1.0.0" - checksum: 10/c18b51c4e1fb19705c94b93c0cf093ba014606abceee949399d56074ef1863bf4897a8d884be24e8d224d18c9ce411cf6924006d0a5430492729af51256e067a - languageName: node - linkType: hard - "wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -32244,70 +32167,6 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:^7.4.2": - version: 7.4.2 - resolution: "webpack-dev-middleware@npm:7.4.2" - dependencies: - colorette: "npm:^2.0.10" - memfs: "npm:^4.6.0" - mime-types: "npm:^2.1.31" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - schema-utils: "npm:^4.0.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - checksum: 10/608d101b82081a5bc6c0237f9945e14a8eefce1664c10877f3feb0042710f6c8b4288b07986505f791302d81b3c51180f679b97c91c3cdabd3fd0687a464ca1c - languageName: node - linkType: hard - -"webpack-dev-server@npm:5.2.1": - version: 5.2.1 - resolution: "webpack-dev-server@npm:5.2.1" - dependencies: - "@types/bonjour": "npm:^3.5.13" - "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.21" - "@types/express-serve-static-core": "npm:^4.17.21" - "@types/serve-index": "npm:^1.9.4" - "@types/serve-static": "npm:^1.15.5" - "@types/sockjs": "npm:^0.3.36" - "@types/ws": "npm:^8.5.10" - ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.2.1" - chokidar: "npm:^3.6.0" - colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" - connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.21.2" - graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.7" - ipaddr.js: "npm:^2.1.0" - launch-editor: "npm:^2.6.1" - open: "npm:^10.0.3" - p-retry: "npm:^6.2.0" - schema-utils: "npm:^4.2.0" - selfsigned: "npm:^2.4.1" - serve-index: "npm:^1.9.1" - sockjs: "npm:^0.3.24" - spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^7.4.2" - ws: "npm:^8.18.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - bin: - webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/424edfe22b7bbe2301a38b8b519dfeb7643e0ca643be01af3fa48ec18512955c1952246741d7577bdb911ee09dcd6c521ade7d65e0059448ee69ab02bfac4624 - languageName: node - linkType: hard - "webpack-hot-middleware@npm:^2.25.1": version: 2.25.3 resolution: "webpack-hot-middleware@npm:2.25.3" @@ -32333,15 +32192,15 @@ __metadata: languageName: node linkType: hard -"webpack-manifest-plugin@npm:5.0.0": - version: 5.0.0 - resolution: "webpack-manifest-plugin@npm:5.0.0" +"webpack-manifest-plugin@npm:5.0.1": + version: 5.0.1 + resolution: "webpack-manifest-plugin@npm:5.0.1" dependencies: tapable: "npm:^2.0.0" webpack-sources: "npm:^2.2.0" peerDependencies: - webpack: ^5.47.0 - checksum: 10/466ade444c23d4a8ec3d1a108ec2142468cb260570533acf2cf68806065ccdef649dbac0fa6b0700f4c6d2497535ae950a20e83b26edb25ccfa9bb51538fa607 + webpack: ^5.75.0 + checksum: 10/590355d177e58d5ea2fd1c3abbc6fb66cac3a52df2a499a197cde8a3493d37fcce64e6e2e755e084f04e8f618a5e42b2618a81756063fa8b2f7d7527e5dcdad1 languageName: node linkType: hard @@ -32513,7 +32372,7 @@ __metadata: languageName: node linkType: hard -"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": +"websocket-driver@npm:>=0.5.1": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4" dependencies: @@ -32636,21 +32495,29 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10/1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be languageName: node linkType: hard -"which@npm:^1.2.14, which@npm:^1.3.1": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68 + languageName: node + linkType: hard + +"which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -32767,6 +32634,18 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^3.0.0": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: "npm:^0.1.4" + is-typedarray: "npm:^1.0.0" + signal-exit: "npm:^3.0.2" + typedarray-to-buffer: "npm:^3.1.5" + checksum: 10/0955ab94308b74d32bc252afe69d8b42ba4b8a28b8d79f399f3f405969f82623f981e35d13129a52aa2973450f342107c06d86047572637584e85a1c0c246bf3 + languageName: node + linkType: hard + "write-file-atomic@npm:^4.0.2": version: 4.0.2 resolution: "write-file-atomic@npm:4.0.2" @@ -32817,7 +32696,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.0, ws@npm:^8.2.3, ws@npm:^8.9.0": +"ws@npm:^8.2.3, ws@npm:^8.9.0": version: 8.18.0 resolution: "ws@npm:8.18.0" peerDependencies: @@ -32936,6 +32815,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10/392870b2a100bbc643bc035fe3a89cef5591b719c7bdc8721bcdb3d27ab39fa4870acdca67b0ee096e146d769f311d68eda6b8195a6d970f227795061923013f + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -32980,6 +32866,16 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10/235bcbad5b7ca13e5abc54df61d42f230857c6f83223a38e4ed7b824681875b7f8b6ed52139d88a3ad007050f28dc0324b3c805deac7db22ae3b4815dae0e1bf + languageName: node + linkType: hard + "yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" @@ -33002,6 +32898,25 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^15.0.2": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10/bbcc82222996c0982905b668644ca363eebe6ffd6a572fbb52f0c0e8146661d8ce5af2a7df546968779bb03d1e4186f3ad3d55dfaadd1c4f0d5187c0e3a5ba16 + languageName: node + linkType: hard + "yargs@npm:^16.0.0, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0"