Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32975dd0a8 | |||
| f7b7c53a09 |
@@ -446,9 +446,32 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
|
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingNavigationV2) {
|
||||||
})
|
// New navigation: Alert rules parent (tabs managed on frontend)
|
||||||
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
|
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-rules", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add child nav items for permission checking (tabs will be rendered by frontend)
|
||||||
|
// Alert rules list tab
|
||||||
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
|
Text: "Alert rules", Id: "alert-rules-list", Url: s.cfg.AppSubURL + "/alerting/list",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Recently deleted tab (check additional feature flags)
|
||||||
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
|
if c.GetOrgRole() == org.RoleAdmin && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertRuleRestore) && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingRuleRecoverDeleted) {
|
||||||
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
|
Text: "Recently deleted", Id: "alert-rules-recently-deleted", Url: s.cfg.AppSubURL + "/alerting/recently-deleted",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Legacy navigation
|
||||||
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
|
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contactPointsPerms := []ac.Evaluator{
|
contactPointsPerms := []ac.Evaluator{
|
||||||
@@ -508,12 +531,16 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
|
|||||||
}
|
}
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
if c.GetOrgRole() == org.RoleAdmin && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertRuleRestore) && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingRuleRecoverDeleted) {
|
if c.GetOrgRole() == org.RoleAdmin && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertRuleRestore) && s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingRuleRecoverDeleted) {
|
||||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
// Only show as standalone item in legacy navigation (V2 shows it as a tab under Alert rules)
|
||||||
Text: "Recently deleted",
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
SubTitle: "Any items listed here for more than 30 days will be automatically deleted.",
|
if !s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingNavigationV2) {
|
||||||
Id: "alerts/recently-deleted",
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
Url: s.cfg.AppSubURL + "/alerting/recently-deleted",
|
Text: "Recently deleted",
|
||||||
})
|
SubTitle: "Any items listed here for more than 30 days will be automatically deleted.",
|
||||||
|
Id: "alerts/recently-deleted",
|
||||||
|
Url: s.cfg.AppSubURL + "/alerting/recently-deleted",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.GetOrgRole() == org.RoleAdmin {
|
if c.GetOrgRole() == org.RoleAdmin {
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { useLocation } from 'react-router-dom-v5-compat';
|
||||||
|
|
||||||
|
import { NavModelItem } from '@grafana/data';
|
||||||
|
import { t } from '@grafana/i18n';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { useSelector } from 'app/types/store';
|
||||||
|
|
||||||
|
export function useAlertRulesNav() {
|
||||||
|
const location = useLocation();
|
||||||
|
const navIndex = useSelector((state) => state.navIndex);
|
||||||
|
|
||||||
|
// Check if V2 navigation is enabled
|
||||||
|
const useV2Nav = config.featureToggles.alertingNavigationV2;
|
||||||
|
|
||||||
|
if (!useV2Nav) {
|
||||||
|
// Legacy navigation: return simple navId
|
||||||
|
return {
|
||||||
|
navId: 'alert-list',
|
||||||
|
pageNav: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// V2 Navigation: Create tabs structure
|
||||||
|
const alertRulesNav = navIndex['alert-rules'];
|
||||||
|
|
||||||
|
if (!alertRulesNav) {
|
||||||
|
// Fallback to legacy if nav item doesn't exist
|
||||||
|
return {
|
||||||
|
navId: 'alert-list',
|
||||||
|
pageNav: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// All available tabs
|
||||||
|
const allTabs = [
|
||||||
|
{
|
||||||
|
id: 'alert-rules-list',
|
||||||
|
text: t('alerting.navigation.alert-rules', 'Alert rules'),
|
||||||
|
url: '/alerting/list',
|
||||||
|
active: location.pathname === '/alerting/list',
|
||||||
|
icon: 'list-ul',
|
||||||
|
parentItem: alertRulesNav,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'alert-rules-recently-deleted',
|
||||||
|
text: t('alerting.navigation.recently-deleted', 'Recently deleted'),
|
||||||
|
url: '/alerting/recently-deleted',
|
||||||
|
active: location.pathname === '/alerting/recently-deleted',
|
||||||
|
icon: 'history',
|
||||||
|
parentItem: alertRulesNav,
|
||||||
|
},
|
||||||
|
].filter((tab) => {
|
||||||
|
// Filter based on permissions - if nav item doesn't exist, user doesn't have permission
|
||||||
|
const navItem = navIndex[tab.id];
|
||||||
|
return navItem !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create pageNav that represents the Alert rules page with tabs as children
|
||||||
|
const pageNav: NavModelItem = {
|
||||||
|
...alertRulesNav,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
children: allTabs as NavModelItem[],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
navId: 'alert-rules',
|
||||||
|
pageNav,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import { shouldUsePrometheusRulesPrimary } from '../featureToggles';
|
|||||||
import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces';
|
import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces';
|
||||||
import { useFilteredRules, useRulesFilter } from '../hooks/useFilteredRules';
|
import { useFilteredRules, useRulesFilter } from '../hooks/useFilteredRules';
|
||||||
import { useUnifiedAlertingSelector } from '../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../hooks/useUnifiedAlertingSelector';
|
||||||
|
import { useAlertRulesNav } from '../navigation/useAlertRulesNav';
|
||||||
import { fetchAllPromAndRulerRulesAction, fetchAllPromRulesAction, fetchRulerRulesAction } from '../state/actions';
|
import { fetchAllPromAndRulerRulesAction, fetchAllPromRulesAction, fetchRulerRulesAction } from '../state/actions';
|
||||||
import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants';
|
import { RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME, getAllRulesSourceNames } from '../utils/datasource';
|
import { GRAFANA_RULES_SOURCE_NAME, getAllRulesSourceNames } from '../utils/datasource';
|
||||||
@@ -38,6 +39,7 @@ const LIMIT_ALERTS = INSTANCES_DISPLAY_LIMIT + 1;
|
|||||||
const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary();
|
const prometheusRulesPrimary = shouldUsePrometheusRulesPrimary();
|
||||||
|
|
||||||
const RuleListV1 = () => {
|
const RuleListV1 = () => {
|
||||||
|
const { navId, pageNav } = useAlertRulesNav();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []);
|
const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []);
|
||||||
const [expandAll, setExpandAll] = useState(false);
|
const [expandAll, setExpandAll] = useState(false);
|
||||||
@@ -119,7 +121,8 @@ const RuleListV1 = () => {
|
|||||||
// We don't want to show the Loading... indicator for the whole page.
|
// We don't want to show the Loading... indicator for the whole page.
|
||||||
// We show separate indicators for Grafana-managed and Cloud rules
|
// We show separate indicators for Grafana-managed and Cloud rules
|
||||||
<AlertingPageWrapper
|
<AlertingPageWrapper
|
||||||
navId="alert-list"
|
navId={navId}
|
||||||
|
pageNav={pageNav}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
renderTitle={(title) => <RuleListPageTitle title={title} />}
|
renderTitle={(title) => <RuleListPageTitle title={title} />}
|
||||||
actions={<RuleListActionButtons hasAlertRulesCreated={hasAlertRulesCreated} />}
|
actions={<RuleListActionButtons hasAlertRulesCreated={hasAlertRulesCreated} />}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useListViewMode } from '../components/rules/Filter/RulesViewModeSelecto
|
|||||||
import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton';
|
import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton';
|
||||||
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
|
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
|
||||||
import { useRulesFilter } from '../hooks/useFilteredRules';
|
import { useRulesFilter } from '../hooks/useFilteredRules';
|
||||||
|
import { useAlertRulesNav } from '../navigation/useAlertRulesNav';
|
||||||
import { getRulesDataSources } from '../utils/datasource';
|
import { getRulesDataSources } from '../utils/datasource';
|
||||||
|
|
||||||
import { FilterView } from './FilterView';
|
import { FilterView } from './FilterView';
|
||||||
@@ -123,10 +124,12 @@ export function RuleListActions() {
|
|||||||
|
|
||||||
export default function RuleListPage() {
|
export default function RuleListPage() {
|
||||||
const { isApplying } = useApplyDefaultSearch();
|
const { isApplying } = useApplyDefaultSearch();
|
||||||
|
const { navId, pageNav } = useAlertRulesNav();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper
|
<AlertingPageWrapper
|
||||||
navId="alert-list"
|
navId={navId}
|
||||||
|
pageNav={pageNav}
|
||||||
renderTitle={(title) => <RuleListPageTitle title={title} />}
|
renderTitle={(title) => <RuleListPageTitle title={title} />}
|
||||||
isLoading={isApplying}
|
isLoading={isApplying}
|
||||||
actions={<RuleListActions />}
|
actions={<RuleListActions />}
|
||||||
|
|||||||
@@ -1929,6 +1929,10 @@
|
|||||||
"select-group": "Select group",
|
"select-group": "Select group",
|
||||||
"select-namespace": "Select namespace"
|
"select-namespace": "Select namespace"
|
||||||
},
|
},
|
||||||
|
"navigation": {
|
||||||
|
"alert-rules": "Alert rules",
|
||||||
|
"recently-deleted": "Recently deleted"
|
||||||
|
},
|
||||||
"need-help-info": {
|
"need-help-info": {
|
||||||
"need-help": "Need help?"
|
"need-help": "Need help?"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user