Alerting: Move action buttons in the alert list view (#81341)
Move action buttons in the alert list view
This commit is contained in:
@@ -1,78 +0,0 @@
|
|||||||
import { isEmpty } from 'lodash';
|
|
||||||
import React from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { useToggle } from 'react-use';
|
|
||||||
|
|
||||||
import { urlUtil } from '@grafana/data';
|
|
||||||
import { Button, Dropdown, Icon, LinkButton, Menu, MenuItem } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { logInfo, LogMessages } from './Analytics';
|
|
||||||
import { GrafanaRulesExporter } from './components/export/GrafanaRulesExporter';
|
|
||||||
import { AlertingAction, useAlertingAbility } from './hooks/useAbilities';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
enableExport?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MoreActionsRuleButtons({ enableExport }: Props) {
|
|
||||||
const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule);
|
|
||||||
const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule);
|
|
||||||
const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);
|
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const [showExportDrawer, toggleShowExportDrawer] = useToggle(false);
|
|
||||||
|
|
||||||
const canCreateGrafanaRules = createRuleSupported && createRuleAllowed;
|
|
||||||
const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed;
|
|
||||||
const canExportRules = exportRulesSupported && exportRulesAllowed;
|
|
||||||
|
|
||||||
const menuItems: JSX.Element[] = [];
|
|
||||||
|
|
||||||
if (canCreateGrafanaRules || canCreateCloudRules) {
|
|
||||||
menuItems.push(
|
|
||||||
<MenuItem
|
|
||||||
label="New recording rule"
|
|
||||||
key="new-recording-rule"
|
|
||||||
url={urlUtil.renderUrl(`alerting/new/recording`, {
|
|
||||||
returnTo: location.pathname + location.search,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canExportRules) {
|
|
||||||
menuItems.push(
|
|
||||||
<MenuItem
|
|
||||||
label="Export all Grafana-managed rules"
|
|
||||||
key="export-all-rules"
|
|
||||||
onClick={toggleShowExportDrawer}
|
|
||||||
disabled={!enableExport}
|
|
||||||
description={enableExport ? '' : 'No Grafana-managed rules found'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{(canCreateGrafanaRules || canCreateCloudRules) && (
|
|
||||||
<LinkButton
|
|
||||||
href={urlUtil.renderUrl('alerting/new/alerting', { returnTo: location.pathname + location.search })}
|
|
||||||
icon="plus"
|
|
||||||
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
|
||||||
>
|
|
||||||
New alert rule
|
|
||||||
</LinkButton>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isEmpty(menuItems) && (
|
|
||||||
<Dropdown overlay={<Menu>{menuItems}</Menu>}>
|
|
||||||
<Button variant="secondary">
|
|
||||||
More
|
|
||||||
<Icon name="angle-down" />
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
{canExportRules && showExportDrawer && <GrafanaRulesExporter onClose={toggleShowExportDrawer} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,9 @@ import { discoverFeatures } from './api/buildInfo';
|
|||||||
import { fetchRules } from './api/prometheus';
|
import { fetchRules } from './api/prometheus';
|
||||||
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
|
||||||
import {
|
import {
|
||||||
|
MockDataSourceSrv,
|
||||||
grantUserPermissions,
|
grantUserPermissions,
|
||||||
mockDataSource,
|
mockDataSource,
|
||||||
MockDataSourceSrv,
|
|
||||||
mockPromAlert,
|
mockPromAlert,
|
||||||
mockPromAlertingRule,
|
mockPromAlertingRule,
|
||||||
mockPromRecordingRule,
|
mockPromRecordingRule,
|
||||||
@@ -120,11 +120,8 @@ const ui = {
|
|||||||
rulesFilterInput: byTestId('search-query-input'),
|
rulesFilterInput: byTestId('search-query-input'),
|
||||||
moreErrorsButton: byRole('button', { name: /more errors/ }),
|
moreErrorsButton: byRole('button', { name: /more errors/ }),
|
||||||
editCloudGroupIcon: byTestId('edit-group'),
|
editCloudGroupIcon: byTestId('edit-group'),
|
||||||
newRuleButton: byRole('link', { name: 'New alert rule' }),
|
newRuleButton: byText(/new alert rule/i),
|
||||||
moreButton: byRole('button', { name: 'More' }),
|
exportButton: byText(/export rules/i),
|
||||||
exportButton: byRole('menuitem', {
|
|
||||||
name: /export all grafana\-managed rules/i,
|
|
||||||
}),
|
|
||||||
editGroupModal: {
|
editGroupModal: {
|
||||||
dialog: byRole('dialog'),
|
dialog: byRole('dialog'),
|
||||||
namespaceInput: byRole('textbox', { name: /^Namespace/ }),
|
namespaceInput: byRole('textbox', { name: /^Namespace/ }),
|
||||||
@@ -728,7 +725,8 @@ describe('RuleList', () => {
|
|||||||
|
|
||||||
renderRuleList();
|
renderRuleList();
|
||||||
|
|
||||||
await userEvent.click(ui.moreButton.get());
|
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
|
||||||
|
|
||||||
expect(ui.exportButton.get()).toBeInTheDocument();
|
expect(ui.exportButton.get()).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useAsyncFn, useInterval } from 'react-use';
|
import { useAsyncFn, useInterval } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, urlUtil } from '@grafana/data';
|
||||||
import { Button, useStyles2, withErrorBoundary, Stack } from '@grafana/ui';
|
import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { useDispatch } from 'app/types';
|
import { useDispatch } from 'app/types';
|
||||||
|
|
||||||
import { CombinedRuleNamespace } from '../../../types/unified-alerting';
|
import { CombinedRuleNamespace } from '../../../types/unified-alerting';
|
||||||
|
|
||||||
import { trackRuleListNavigation } from './Analytics';
|
import { LogMessages, logInfo, trackRuleListNavigation } from './Analytics';
|
||||||
import { MoreActionsRuleButtons } from './MoreActionsRuleButtons';
|
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
import { NoRulesSplash } from './components/rules/NoRulesCTA';
|
import { NoRulesSplash } from './components/rules/NoRulesCTA';
|
||||||
import { INSTANCES_DISPLAY_LIMIT } from './components/rules/RuleDetails';
|
import { INSTANCES_DISPLAY_LIMIT } from './components/rules/RuleDetails';
|
||||||
@@ -19,12 +19,13 @@ import { RuleListGroupView } from './components/rules/RuleListGroupView';
|
|||||||
import { RuleListStateView } from './components/rules/RuleListStateView';
|
import { RuleListStateView } from './components/rules/RuleListStateView';
|
||||||
import { RuleStats } from './components/rules/RuleStats';
|
import { RuleStats } from './components/rules/RuleStats';
|
||||||
import RulesFilter from './components/rules/RulesFilter';
|
import RulesFilter from './components/rules/RulesFilter';
|
||||||
|
import { AlertingAction, useAlertingAbility } from './hooks/useAbilities';
|
||||||
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 { fetchAllPromAndRulerRulesAction } from './state/actions';
|
import { fetchAllPromAndRulerRulesAction } from './state/actions';
|
||||||
import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants';
|
import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants';
|
||||||
import { getAllRulesSourceNames, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
import { getAllRulesSourceNames } from './utils/datasource';
|
||||||
|
|
||||||
const VIEWS = {
|
const VIEWS = {
|
||||||
groups: RuleListGroupView,
|
groups: RuleListGroupView,
|
||||||
@@ -77,9 +78,6 @@ const RuleList = withErrorBoundary(
|
|||||||
return noRules && state.dispatched;
|
return noRules && state.dispatched;
|
||||||
});
|
});
|
||||||
|
|
||||||
const grafanaRules = useUnifiedAlertingSelector((state) => state.rulerRules[GRAFANA_RULES_SOURCE_NAME]?.result);
|
|
||||||
const hasGrafanaRules = Object.keys(grafanaRules ?? {}).length > 0;
|
|
||||||
|
|
||||||
const limitAlerts = hasActiveFilters ? undefined : LIMIT_ALERTS;
|
const limitAlerts = hasActiveFilters ? undefined : LIMIT_ALERTS;
|
||||||
// Trigger data refresh only when the RULE_LIST_POLL_INTERVAL_MS elapsed since the previous load FINISHED
|
// Trigger data refresh only when the RULE_LIST_POLL_INTERVAL_MS elapsed since the previous load FINISHED
|
||||||
const [_, fetchRules] = useAsyncFn(async () => {
|
const [_, fetchRules] = useAsyncFn(async () => {
|
||||||
@@ -101,6 +99,7 @@ const RuleList = withErrorBoundary(
|
|||||||
// Show splash only when we loaded all of the data sources and none of them has alerts
|
// Show splash only when we loaded all of the data sources and none of them has alerts
|
||||||
const hasNoAlertRulesCreatedYet =
|
const hasNoAlertRulesCreatedYet =
|
||||||
allPromLoaded && allPromEmpty && promRequests.length > 0 && allRulerEmpty && allRulerLoaded;
|
allPromLoaded && allPromEmpty && promRequests.length > 0 && allRulerEmpty && allRulerLoaded;
|
||||||
|
const hasAlertRulesCreated = !hasNoAlertRulesCreatedYet;
|
||||||
|
|
||||||
const combinedNamespaces: CombinedRuleNamespace[] = useCombinedRuleNamespaces();
|
const combinedNamespaces: CombinedRuleNamespace[] = useCombinedRuleNamespaces();
|
||||||
const filteredNamespaces = useFilteredRules(combinedNamespaces, filterState);
|
const filteredNamespaces = useFilteredRules(combinedNamespaces, filterState);
|
||||||
@@ -108,10 +107,10 @@ const RuleList = withErrorBoundary(
|
|||||||
return (
|
return (
|
||||||
// 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 navId="alert-list" isLoading={false}>
|
<AlertingPageWrapper navId="alert-list" isLoading={false} actions={hasAlertRulesCreated && <CreateAlertButton />}>
|
||||||
<RuleListErrors />
|
<RuleListErrors />
|
||||||
<RulesFilter onFilterCleared={onFilterCleared} />
|
<RulesFilter onFilterCleared={onFilterCleared} />
|
||||||
{!hasNoAlertRulesCreatedYet && (
|
{hasAlertRulesCreated && (
|
||||||
<>
|
<>
|
||||||
<div className={styles.break} />
|
<div className={styles.break} />
|
||||||
<div className={styles.buttonsContainer}>
|
<div className={styles.buttonsContainer}>
|
||||||
@@ -128,14 +127,11 @@ const RuleList = withErrorBoundary(
|
|||||||
)}
|
)}
|
||||||
<RuleStats namespaces={filteredNamespaces} />
|
<RuleStats namespaces={filteredNamespaces} />
|
||||||
</div>
|
</div>
|
||||||
<Stack direction="row" gap={0.5}>
|
|
||||||
<MoreActionsRuleButtons enableExport={hasGrafanaRules} />
|
|
||||||
</Stack>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{hasNoAlertRulesCreatedYet && <NoRulesSplash />}
|
{hasNoAlertRulesCreatedYet && <NoRulesSplash />}
|
||||||
{!hasNoAlertRulesCreatedYet && <ViewComponent expandAll={expandAll} namespaces={filteredNamespaces} />}
|
{hasAlertRulesCreated && <ViewComponent expandAll={expandAll} namespaces={filteredNamespaces} />}
|
||||||
</AlertingPageWrapper>
|
</AlertingPageWrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -165,3 +161,27 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default RuleList;
|
export default RuleList;
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<LinkButton
|
||||||
|
href={urlUtil.renderUrl('alerting/new/alerting', { returnTo: location.pathname + location.search })}
|
||||||
|
icon="plus"
|
||||||
|
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
||||||
|
>
|
||||||
|
New alert rule
|
||||||
|
</LinkButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, urlUtil } from '@grafana/data';
|
||||||
import { LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';
|
import { LinkButton, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';
|
||||||
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
|
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
||||||
|
import { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities';
|
||||||
import { usePagination } from '../../hooks/usePagination';
|
import { usePagination } from '../../hooks/usePagination';
|
||||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import { getPaginationStyles } from '../../styles/pagination';
|
import { getPaginationStyles } from '../../styles/pagination';
|
||||||
@@ -52,7 +54,10 @@ export const CloudRules = ({ namespaces, expandAll }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<section className={styles.wrapper}>
|
<section className={styles.wrapper}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<h5>Mimir / Cortex / Loki</h5>
|
<div className={styles.headerRow}>
|
||||||
|
<h5>Mimir / Cortex / Loki</h5>
|
||||||
|
<CreateRecordingRuleButton />
|
||||||
|
</div>
|
||||||
{dataSourcesLoading.length ? (
|
{dataSourcesLoading.length ? (
|
||||||
<LoadingPlaceholder
|
<LoadingPlaceholder
|
||||||
className={styles.loader}
|
className={styles.loader}
|
||||||
@@ -106,4 +111,35 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
padding: ${theme.spacing(2)};
|
padding: ${theme.spacing(2)};
|
||||||
`,
|
`,
|
||||||
pagination: getPaginationStyles(theme),
|
pagination: getPaginationStyles(theme),
|
||||||
|
headerRow: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function CreateRecordingRuleButton() {
|
||||||
|
const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule);
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed;
|
||||||
|
|
||||||
|
if (canCreateCloudRules) {
|
||||||
|
return (
|
||||||
|
<LinkButton
|
||||||
|
key="new-recording-rule"
|
||||||
|
href={urlUtil.renderUrl(`alerting/new/recording`, {
|
||||||
|
returnTo: location.pathname + location.search,
|
||||||
|
})}
|
||||||
|
icon="plus"
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
New recording rule
|
||||||
|
</LinkButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useToggle } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';
|
import { Button, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
|
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
|
||||||
|
import { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities';
|
||||||
import { flattenGrafanaManagedRules } from '../../hooks/useCombinedRuleNamespaces';
|
import { flattenGrafanaManagedRules } from '../../hooks/useCombinedRuleNamespaces';
|
||||||
import { usePagination } from '../../hooks/usePagination';
|
import { usePagination } from '../../hooks/usePagination';
|
||||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import { getPaginationStyles } from '../../styles/pagination';
|
import { getPaginationStyles } from '../../styles/pagination';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||||
import { initialAsyncRequestState } from '../../utils/redux';
|
import { initialAsyncRequestState } from '../../utils/redux';
|
||||||
|
import { GrafanaRulesExporter } from '../export/GrafanaRulesExporter';
|
||||||
|
|
||||||
import { RulesGroup } from './RulesGroup';
|
import { RulesGroup } from './RulesGroup';
|
||||||
import { useCombinedGroupNamespace } from './useCombinedGroupNamespace';
|
import { useCombinedGroupNamespace } from './useCombinedGroupNamespace';
|
||||||
@@ -45,10 +48,30 @@ export const GrafanaRules = ({ namespaces, expandAll }: Props) => {
|
|||||||
DEFAULT_PER_PAGE_PAGINATION
|
DEFAULT_PER_PAGE_PAGINATION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);
|
||||||
|
const canExportRules = exportRulesSupported && exportRulesAllowed;
|
||||||
|
|
||||||
|
const [showExportDrawer, toggleShowExportDrawer] = useToggle(false);
|
||||||
|
const hasGrafanaAlerts = namespaces.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.wrapper}>
|
<section className={styles.wrapper}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<h5>Grafana</h5>
|
<div className={styles.headerRow}>
|
||||||
|
<h5>Grafana</h5>
|
||||||
|
{hasGrafanaAlerts && canExportRules && (
|
||||||
|
<Button
|
||||||
|
aria-label="export all grafana rules"
|
||||||
|
data-testid="export-all-grafana-rules"
|
||||||
|
icon="download-alt"
|
||||||
|
tooltip="Export all Grafana-managed rules"
|
||||||
|
onClick={toggleShowExportDrawer}
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
Export rules
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{loading ? <LoadingPlaceholder className={styles.loader} text="Loading..." /> : <div />}
|
{loading ? <LoadingPlaceholder className={styles.loader} text="Loading..." /> : <div />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,6 +93,7 @@ export const GrafanaRules = ({ namespaces, expandAll }: Props) => {
|
|||||||
onNavigate={onPageChange}
|
onNavigate={onPageChange}
|
||||||
hideWhenSinglePage
|
hideWhenSinglePage
|
||||||
/>
|
/>
|
||||||
|
{canExportRules && showExportDrawer && <GrafanaRulesExporter onClose={toggleShowExportDrawer} />}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -91,4 +115,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
padding: ${theme.spacing(2)};
|
padding: ${theme.spacing(2)};
|
||||||
`,
|
`,
|
||||||
pagination: getPaginationStyles(theme),
|
pagination: getPaginationStyles(theme),
|
||||||
|
headerRow: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user