From 5c46a5daae5bd4c173572f51d800faaeefe4228c Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Wed, 10 Dec 2025 10:45:54 -0500 Subject: [PATCH 1/4] fix: handle all ns selected for tenancy user --- web/src/components/alerting/SilenceForm.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/web/src/components/alerting/SilenceForm.tsx b/web/src/components/alerting/SilenceForm.tsx index 50c78275..8f99e88a 100644 --- a/web/src/components/alerting/SilenceForm.tsx +++ b/web/src/components/alerting/SilenceForm.tsx @@ -51,7 +51,7 @@ import { ExternalLink } from '../console/utils/link'; import { useBoolean } from '../hooks/useBoolean'; import { getSilenceAlertUrl, usePerspective } from '../hooks/usePerspective'; import { DataTestIDs } from '../data-test'; -import { getAlertmanagerSilencesUrl } from '../utils'; +import { ALL_NAMESPACES_KEY, getAlertmanagerSilencesUrl } from '../utils'; import { useAlerts } from '../../hooks/useAlerts'; import { useMonitoring } from '../../hooks/useMonitoring'; @@ -269,14 +269,15 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace createdBy, endsAt: saveEndsAt.toISOString(), id: defaults.id, - matchers: isNamespaced - ? matchers.concat({ - name: 'namespace', - value: namespace, - isRegex: false, - isEqual: true, - }) - : matchers, + matchers: + isNamespaced && namespace !== ALL_NAMESPACES_KEY + ? matchers.concat({ + name: 'namespace', + value: namespace, + isRegex: false, + isEqual: true, + }) + : matchers, startsAt: saveStartsAt.toISOString(), }; @@ -425,7 +426,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace - {isNamespaced && ( + {isNamespaced && namespace !== ALL_NAMESPACES_KEY && ( From 457ee7b8962acbb004a7321caa516f7ae3468cf2 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Thu, 11 Dec 2025 14:50:12 -0500 Subject: [PATCH 2/4] fix: remove namespace bar from create and edit silence pages --- web/src/components/alerting/SilenceForm.tsx | 37 ++++++++++----------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/web/src/components/alerting/SilenceForm.tsx b/web/src/components/alerting/SilenceForm.tsx index 8f99e88a..be7af85b 100644 --- a/web/src/components/alerting/SilenceForm.tsx +++ b/web/src/components/alerting/SilenceForm.tsx @@ -1,7 +1,6 @@ import { consoleFetchJSON, DocumentTitle, - NamespaceBar, useActiveNamespace, } from '@openshift-console/dynamic-plugin-sdk'; import { @@ -136,6 +135,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace const [namespace] = useActiveNamespace(); const { prometheus } = useMonitoring(); const navigate = useNavigate(); + const isPageNamespaceLocked = isNamespaced && namespace !== ALL_NAMESPACES_KEY; const durations = useMemo(() => { return { @@ -187,7 +187,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace // Since the namespace matcher MUST be the same as the namespace the request is being // made in, we remove the namespace value here and re-add it before sending the request const [matchers, setMatchers] = useState>( - (isNamespaced + (isPageNamespaceLocked ? (defaults.matchers as Matcher[])?.filter((matcher) => matcher.name !== 'namespace') : defaults.matchers) ?? [{ isRegex: false, isEqual: true, name: '', value: '' }], ); @@ -221,11 +221,6 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace }; const removeMatcher = (i: number): void => { - // If we require the namespace don't allow removing it - if (isNamespaced && i === 0) { - return; - } - const newMatchers = _.clone(matchers); newMatchers.splice(i, 1); @@ -249,7 +244,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace const url = getAlertmanagerSilencesUrl({ prometheus, namespace, - useTenancyPath: isNamespaced, + useTenancyPath: isPageNamespaceLocked, }); if (!url) { setError('Alertmanager URL not set'); @@ -269,21 +264,24 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace createdBy, endsAt: saveEndsAt.toISOString(), id: defaults.id, - matchers: - isNamespaced && namespace !== ALL_NAMESPACES_KEY - ? matchers.concat({ - name: 'namespace', - value: namespace, - isRegex: false, - isEqual: true, - }) - : matchers, + matchers: isPageNamespaceLocked + ? matchers.concat({ + name: 'namespace', + value: namespace, + isRegex: false, + isEqual: true, + }) + : matchers, startsAt: saveStartsAt.toISOString(), }; consoleFetchJSON .post( - getAlertmanagerSilencesUrl({ prometheus, namespace, useTenancyPath: isNamespaced }), + getAlertmanagerSilencesUrl({ + prometheus, + namespace, + useTenancyPath: isPageNamespaceLocked, + }), body, ) .then(({ silenceID }) => { @@ -310,7 +308,6 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace return ( <> {title} - {isNamespaced && } {title} @@ -426,7 +423,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace - {isNamespaced && namespace !== ALL_NAMESPACES_KEY && ( + {isPageNamespaceLocked && ( From d04b11b6b37fe087a733d20bea09e7590391f421 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Thu, 11 Dec 2025 14:59:44 -0500 Subject: [PATCH 3/4] fix: make forbidden error message more explicit and add translation strings --- web/locales/en/plugin__monitoring-plugin.json | 9 ++++++--- web/src/components/alerting/SilenceForm.tsx | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/web/locales/en/plugin__monitoring-plugin.json b/web/locales/en/plugin__monitoring-plugin.json index f665d086..2f7a72a1 100644 --- a/web/locales/en/plugin__monitoring-plugin.json +++ b/web/locales/en/plugin__monitoring-plugin.json @@ -83,6 +83,10 @@ "1d": "1d", "2d": "2d", "1w": "1w", + "Comment is required.": "Comment is required.", + "Alertmanager URL not set": "Alertmanager URL not set", + "Error saving Silence": "Error saving Silence", + "Forbidden: Missing permissions for silences": "Forbidden: Missing permissions for silences", "Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.": "Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.", "Duration": "Duration", "Silence alert from...": "Silence alert from...", @@ -135,9 +139,6 @@ "404: Not Found": "404: Not Found", "{{labels}} content is not available in the catalog at this time due to loading failures.": "{{labels}} content is not available in the catalog at this time due to loading failures.", "No datapoints found.": "No datapoints found.", - "Namespaces": "Namespaces", - "Project": "Project", - "Projects": "Projects", "Create new option \"{{option}}\"": "Create new option \"{{option}}\"", "Filter options": "Filter options", "Clear input value": "Clear input value", @@ -177,6 +178,8 @@ "No results match the filter criteria.": "No results match the filter criteria.", "Clear filters": "Clear filters", "Select project...": "Select project...", + "Projects": "Projects", + "Project": "Project", "Dashboard": "Dashboard", "Refresh off": "Refresh off", "{{count}} second_one": "{{count}} second", diff --git a/web/src/components/alerting/SilenceForm.tsx b/web/src/components/alerting/SilenceForm.tsx index be7af85b..6bcdd8e0 100644 --- a/web/src/components/alerting/SilenceForm.tsx +++ b/web/src/components/alerting/SilenceForm.tsx @@ -237,7 +237,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace // Don't allow comments to only contain whitespace if (_.trim(comment) === '') { - setError('Comment is required.'); + setError(t('Comment is required.')); return; } @@ -247,7 +247,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace useTenancyPath: isPageNamespaceLocked, }); if (!url) { - setError('Alertmanager URL not set'); + setError(t('Alertmanager URL not set')); return; } @@ -290,10 +290,13 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace navigate(getSilenceAlertUrl(perspective, silenceID)); }) .catch((err) => { - const errorMessage = + let errorMessage = typeof _.get(err, 'json') === 'string' ? _.get(err, 'json') - : err.message || 'Error saving Silence'; + : err.message || t('Error saving Silence'); + if (errorMessage === 'Forbidden') { + errorMessage = t('Forbidden: Missing permissions for silences'); + } setError(errorMessage); setInProgress(false); }); From 552c203ec57866d55506fcda404bf904b99bd6f5 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Thu, 11 Dec 2025 15:00:08 -0500 Subject: [PATCH 4/4] fix: deeply clone when editing values to prevent readonly from react state --- web/src/components/alerting/SilenceForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/alerting/SilenceForm.tsx b/web/src/components/alerting/SilenceForm.tsx index 6bcdd8e0..46915709 100644 --- a/web/src/components/alerting/SilenceForm.tsx +++ b/web/src/components/alerting/SilenceForm.tsx @@ -211,7 +211,7 @@ const SilenceForm_: FC = ({ defaults, Info, title, isNamespace }; const setMatcherField = (i: number, field: string, v: string | boolean): void => { - const newMatchers = _.clone(matchers); + const newMatchers = _.cloneDeep(matchers); _.set(newMatchers, [i, field], v); setMatchers(newMatchers); };