diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx index 2bc0fc71f53..fddd9f5bb7e 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx @@ -12,6 +12,7 @@ import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout'; import { useCloudCombinedRulesMatching } from './hooks/useCombinedRule'; import { getRulesSourceByName } from './utils/datasource'; import { createViewLink } from './utils/misc'; +import { unescapePathSeparators } from './utils/rule-id'; const pageTitle = 'Find rule'; const subUrl = config.appSubUrl; @@ -27,8 +28,7 @@ function useRuleFindParams() { return useMemo(() => { const segments = location.pathname?.replace(subUrl, '').split('/') ?? []; // ["", "alerting", "{sourceName}", "{name}] - - const name = decodeURIComponent(segments[3]); + const name = unescapePathSeparators(decodeURIComponent(unescapePathSeparators(segments[3]))); const sourceName = decodeURIComponent(segments[2]); const searchParams = new URLSearchParams(location.search); diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 6ca0cb8f27b..d3051a0de69 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -4,6 +4,7 @@ import { UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } f import { GrafanaEdition } from '@grafana/data/src/types/config'; import { config } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; +import { escapePathSeparators } from 'app/features/alerting/unified/utils/rule-id'; import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; import { SortOrder } from 'app/plugins/panel/alertlist/types'; import { Alert, CombinedRule, FilterState, RulesSource, SilenceFilterState } from 'app/types/unified-alerting'; @@ -57,7 +58,9 @@ export function createMuteTimingLink(muteTimingName: string, alertManagerSourceN export function createShareLink(ruleSource: RulesSource, rule: CombinedRule): string { if (isCloudRulesSource(ruleSource)) { - return createAbsoluteUrl(`/alerting/${encodeURIComponent(ruleSource.name)}/${encodeURIComponent(rule.name)}/find`); + return createAbsoluteUrl( + `/alerting/${encodeURIComponent(ruleSource.name)}/${encodeURIComponent(escapePathSeparators(rule.name))}/find` + ); } return window.location.href.split('?')[0]; diff --git a/public/app/features/alerting/unified/utils/rule-id.ts b/public/app/features/alerting/unified/utils/rule-id.ts index e209612c4e4..c2eb6eb29d1 100644 --- a/public/app/features/alerting/unified/utils/rule-id.ts +++ b/public/app/features/alerting/unified/utils/rule-id.ts @@ -106,11 +106,11 @@ function unescapeDollars(value: string): string { * we'll use some non-printable characters from the ASCII table that will get encoded properly but very unlikely * to ever be used in a rule name or namespace */ -function escapePathSeparators(value: string): string { +export function escapePathSeparators(value: string): string { return value.replace(/\//g, '\x1f').replace(/\\/g, '\x1e'); } -function unescapePathSeparators(value: string): string { +export function unescapePathSeparators(value: string): string { return value.replace(/\x1f/g, '/').replace(/\x1e/g, '\\'); }