274 lines
9.4 KiB
TypeScript
274 lines
9.4 KiB
TypeScript
import { useMemo } from 'react';
|
|
|
|
import {
|
|
Alert,
|
|
Button,
|
|
EmptyState,
|
|
LinkButton,
|
|
LoadingPlaceholder,
|
|
Pagination,
|
|
Stack,
|
|
Tab,
|
|
TabContent,
|
|
TabsBar,
|
|
Text,
|
|
} from '@grafana/ui';
|
|
import { contextSrv } from 'app/core/core';
|
|
import { Trans, t } from 'app/core/internationalization';
|
|
import { shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
|
import { makeAMLink, stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
|
|
import { AccessControlAction } from 'app/types';
|
|
|
|
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
|
|
import { usePagination } from '../../hooks/usePagination';
|
|
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
|
|
import { useAlertmanager } from '../../state/AlertmanagerContext';
|
|
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
|
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
|
|
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
|
|
import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
|
|
|
|
import { ContactPoint } from './ContactPoint';
|
|
import { NotificationTemplates } from './NotificationTemplates';
|
|
import { ContactPointsFilter } from './components/ContactPointsFilter';
|
|
import { GlobalConfigAlert } from './components/GlobalConfigAlert';
|
|
import { useContactPointsWithStatus } from './useContactPoints';
|
|
import { useContactPointsSearch } from './useContactPointsSearch';
|
|
import { ALL_CONTACT_POINTS, useExportContactPoint } from './useExportContactPoint';
|
|
import { ContactPointWithMetadata } from './utils';
|
|
|
|
export enum ActiveTab {
|
|
ContactPoints = 'contact_points',
|
|
NotificationTemplates = 'templates',
|
|
}
|
|
|
|
const DEFAULT_PAGE_SIZE = 10;
|
|
|
|
const ContactPointsTab = () => {
|
|
const { selectedAlertmanager } = useAlertmanager();
|
|
const [queryParams] = useURLSearchParams();
|
|
|
|
// If we're using the K8S API, then we don't need to fetch the policies info within the hook,
|
|
// as we get metadata about this from the API
|
|
const fetchPolicies = !shouldUseK8sApi(selectedAlertmanager!);
|
|
// User may have access to list contact points, but not permission to fetch the status endpoint
|
|
const fetchStatuses = contextSrv.hasPermission(AccessControlAction.AlertingNotificationsRead);
|
|
|
|
const { isLoading, error, contactPoints } = useContactPointsWithStatus({
|
|
alertmanager: selectedAlertmanager!,
|
|
fetchPolicies,
|
|
fetchStatuses,
|
|
});
|
|
|
|
const [addContactPointSupported, addContactPointAllowed] = useAlertmanagerAbility(
|
|
AlertmanagerAction.CreateContactPoint
|
|
);
|
|
const [exportContactPointsSupported, exportContactPointsAllowed] = useAlertmanagerAbility(
|
|
AlertmanagerAction.ExportContactPoint
|
|
);
|
|
|
|
const [ExportDrawer, showExportDrawer] = useExportContactPoint();
|
|
|
|
const search = queryParams.get('search');
|
|
|
|
if (isLoading) {
|
|
return <LoadingPlaceholder text="Loading..." />;
|
|
}
|
|
|
|
const isGrafanaManagedAlertmanager = selectedAlertmanager === GRAFANA_RULES_SOURCE_NAME;
|
|
|
|
if (contactPoints.length === 0) {
|
|
return (
|
|
<EmptyState
|
|
variant={addContactPointAllowed ? 'call-to-action' : 'not-found'}
|
|
button={
|
|
addContactPointAllowed && (
|
|
<LinkButton
|
|
href={makeAMLink('/alerting/notifications/receivers/new', selectedAlertmanager)}
|
|
icon="plus"
|
|
size="lg"
|
|
>
|
|
<Trans i18nKey="alerting.contact-points.create">Create contact point</Trans>
|
|
</LinkButton>
|
|
)
|
|
}
|
|
message={t('alerting.contact-points.empty-state.title', "You don't have any contact points yet")}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* TODO we can add some additional info here with a ToggleTip */}
|
|
<Stack direction="row" alignItems="end" justifyContent="space-between">
|
|
<ContactPointsFilter />
|
|
|
|
<Stack direction="row" gap={1}>
|
|
{addContactPointSupported && (
|
|
<LinkButton
|
|
icon="plus"
|
|
aria-label="add contact point"
|
|
variant="primary"
|
|
href="/alerting/notifications/receivers/new"
|
|
disabled={!addContactPointAllowed}
|
|
>
|
|
<Trans i18nKey="alerting.contact-points.create">Create contact point</Trans>
|
|
</LinkButton>
|
|
)}
|
|
{exportContactPointsSupported && (
|
|
<Button
|
|
icon="download-alt"
|
|
variant="secondary"
|
|
aria-label="export all"
|
|
disabled={!exportContactPointsAllowed}
|
|
onClick={() => showExportDrawer(ALL_CONTACT_POINTS)}
|
|
>
|
|
Export all
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
{error ? (
|
|
<Alert title="Failed to fetch contact points">{stringifyErrorLike(error)}</Alert>
|
|
) : (
|
|
<ContactPointsList contactPoints={contactPoints} search={search} pageSize={DEFAULT_PAGE_SIZE} />
|
|
)}
|
|
{/* Grafana manager Alertmanager does not support global config, Mimir and Cortex do */}
|
|
{!isGrafanaManagedAlertmanager && <GlobalConfigAlert alertManagerName={selectedAlertmanager!} />}
|
|
{ExportDrawer}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const NotificationTemplatesTab = () => {
|
|
const [createTemplateSupported, createTemplateAllowed] = useAlertmanagerAbility(
|
|
AlertmanagerAction.CreateNotificationTemplate
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
|
<Text variant="body" color="secondary">
|
|
Create notification templates to customize your notifications.
|
|
</Text>
|
|
{createTemplateSupported && (
|
|
<LinkButton
|
|
icon="plus"
|
|
variant="primary"
|
|
href="/alerting/notifications/templates/new"
|
|
disabled={!createTemplateAllowed}
|
|
>
|
|
Add notification template group
|
|
</LinkButton>
|
|
)}
|
|
</Stack>
|
|
<NotificationTemplates />
|
|
</>
|
|
);
|
|
};
|
|
|
|
const useTabQueryParam = (defaultTab: ActiveTab) => {
|
|
const [queryParams, setQueryParams] = useURLSearchParams();
|
|
const param = useMemo(() => {
|
|
const queryParam = queryParams.get('tab');
|
|
|
|
if (!queryParam || !Object.values(ActiveTab).map(String).includes(queryParam)) {
|
|
return defaultTab;
|
|
}
|
|
|
|
return queryParam || defaultTab;
|
|
}, [defaultTab, queryParams]);
|
|
|
|
const setParam = (tab: ActiveTab) => setQueryParams({ tab });
|
|
return [param, setParam] as const;
|
|
};
|
|
|
|
export const ContactPointsPageContents = () => {
|
|
const { selectedAlertmanager } = useAlertmanager();
|
|
const [, showContactPointsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
|
|
const [, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
|
|
|
|
// Depending on permissions, user may not have access to all tabs,
|
|
// but we can default to picking the first one that they definitely _do_ have access to
|
|
const defaultTab = [
|
|
showContactPointsTab && ActiveTab.ContactPoints,
|
|
showTemplatesTab && ActiveTab.NotificationTemplates,
|
|
].filter((tab) => !!tab)[0];
|
|
|
|
const [activeTab, setActiveTab] = useTabQueryParam(defaultTab);
|
|
|
|
const { contactPoints } = useContactPointsWithStatus({
|
|
alertmanager: selectedAlertmanager!,
|
|
});
|
|
|
|
const showingContactPoints = activeTab === ActiveTab.ContactPoints;
|
|
const showNotificationTemplates = activeTab === ActiveTab.NotificationTemplates;
|
|
|
|
return (
|
|
<>
|
|
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager!} />
|
|
<Stack direction="column">
|
|
<TabsBar>
|
|
{showContactPointsTab && (
|
|
<Tab
|
|
label="Contact Points"
|
|
active={showingContactPoints}
|
|
counter={contactPoints.length}
|
|
onChangeTab={() => setActiveTab(ActiveTab.ContactPoints)}
|
|
/>
|
|
)}
|
|
{showTemplatesTab && (
|
|
<Tab
|
|
label="Notification Templates"
|
|
active={showNotificationTemplates}
|
|
onChangeTab={() => setActiveTab(ActiveTab.NotificationTemplates)}
|
|
/>
|
|
)}
|
|
</TabsBar>
|
|
<TabContent>
|
|
<Stack direction="column">
|
|
{showingContactPoints && <ContactPointsTab />}
|
|
{showNotificationTemplates && <NotificationTemplatesTab />}
|
|
</Stack>
|
|
</TabContent>
|
|
</Stack>
|
|
</>
|
|
);
|
|
};
|
|
|
|
interface ContactPointsListProps {
|
|
contactPoints: ContactPointWithMetadata[];
|
|
search?: string | null;
|
|
pageSize?: number;
|
|
}
|
|
|
|
const ContactPointsList = ({ contactPoints, search, pageSize = DEFAULT_PAGE_SIZE }: ContactPointsListProps) => {
|
|
const searchResults = useContactPointsSearch(contactPoints, search);
|
|
const { page, pageItems, numberOfPages, onPageChange } = usePagination(searchResults, 1, pageSize);
|
|
|
|
if (pageItems.length === 0) {
|
|
const emptyMessage = t('alerting.contact-points.no-contact-points-found', 'No contact points found');
|
|
return <EmptyState variant="not-found" message={emptyMessage} />;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{pageItems.map((contactPoint, index) => {
|
|
const key = `${contactPoint.name}-${index}`;
|
|
return <ContactPoint key={key} contactPoint={contactPoint} />;
|
|
})}
|
|
<Pagination currentPage={page} numberOfPages={numberOfPages} onNavigate={onPageChange} hideWhenSinglePage />
|
|
</>
|
|
);
|
|
};
|
|
|
|
function ContactPointsPage() {
|
|
return (
|
|
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
|
|
<ContactPointsPageContents />
|
|
</AlertmanagerPageWrapper>
|
|
);
|
|
}
|
|
|
|
export default withPageErrorBoundary(ContactPointsPage);
|