mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
chore: incoming webhooks table (#5837)
https://linear.app/unleash/issue/2-1817/ui-create-an-incoming-webhooks-configuration-page This adds an incoming webhooks page with the respective table. We plan on possibly extending the table with a couple more columns in a future PR. This allows us: - View all configured incoming webhooks; - Copy their URL to the clipboard; - Remove them; For "new" and "edit" operations we still need the incoming webhooks form/dialog, coming in a future PR. **Note**: Even though we are showing the full URL in the table for now, we may end up truncating its start in the future (e.g. `.../api/incoming-webhook/<webhook-name>` - This decision depends on how it will look like after the rest of the columns are added. ![image](https://github.com/Unleash/unleash/assets/14320932/1cac3286-818f-4967-8686-43f78aa6bd33)
This commit is contained in:
parent
6ae6193d3f
commit
7af91c7e9d
@ -5,7 +5,6 @@ import { RolesTable } from './RolesTable/RolesTable';
|
|||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { Tab, Tabs, styled, useMediaQuery } from '@mui/material';
|
import { Tab, Tabs, styled, useMediaQuery } from '@mui/material';
|
||||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||||
import { CenteredNavLink } from '../menu/CenteredNavLink';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { PROJECT_ROLE_TYPE, ROOT_ROLE_TYPE } from '@server/util/constants';
|
import { PROJECT_ROLE_TYPE, ROOT_ROLE_TYPE } from '@server/util/constants';
|
||||||
import { useRoles } from 'hooks/api/getters/useRoles/useRoles';
|
import { useRoles } from 'hooks/api/getters/useRoles/useRoles';
|
||||||
|
@ -108,6 +108,11 @@ const PremiumFeatures = {
|
|||||||
url: 'https://docs.getunleash.io/reference/banners',
|
url: 'https://docs.getunleash.io/reference/banners',
|
||||||
label: 'Banners',
|
label: 'Banners',
|
||||||
},
|
},
|
||||||
|
'incoming-webhooks': {
|
||||||
|
plan: FeaturePlan.ENTERPRISE,
|
||||||
|
url: 'https://docs.getunleash.io/reference/incoming-webhooks',
|
||||||
|
label: 'Incoming Webhooks',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type PremiumFeatureType = keyof typeof PremiumFeatures;
|
type PremiumFeatureType = keyof typeof PremiumFeatures;
|
||||||
|
41
frontend/src/component/incomingWebhooks/IncomingWebhooks.tsx
Normal file
41
frontend/src/component/incomingWebhooks/IncomingWebhooks.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
|
import { IncomingWebhooksTable } from './IncomingWebhooksTable/IncomingWebhooksTable';
|
||||||
|
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||||
|
|
||||||
|
interface IIncomingWebhooksProps {
|
||||||
|
modalOpen: boolean;
|
||||||
|
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
selectedIncomingWebhook?: IIncomingWebhook;
|
||||||
|
setSelectedIncomingWebhook: React.Dispatch<
|
||||||
|
React.SetStateAction<IIncomingWebhook | undefined>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IncomingWebhooks = ({
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
selectedIncomingWebhook,
|
||||||
|
setSelectedIncomingWebhook,
|
||||||
|
}: IIncomingWebhooksProps) => {
|
||||||
|
const { isEnterprise } = useUiConfig();
|
||||||
|
|
||||||
|
if (!isEnterprise()) {
|
||||||
|
return <PremiumFeature feature='incoming-webhooks' page />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PermissionGuard permissions={ADMIN}>
|
||||||
|
<IncomingWebhooksTable
|
||||||
|
modalOpen={modalOpen}
|
||||||
|
setModalOpen={setModalOpen}
|
||||||
|
selectedIncomingWebhook={selectedIncomingWebhook}
|
||||||
|
setSelectedIncomingWebhook={setSelectedIncomingWebhook}
|
||||||
|
/>
|
||||||
|
</PermissionGuard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,138 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
} from '@mui/material';
|
||||||
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
import FileCopyIcon from '@mui/icons-material/FileCopy';
|
||||||
|
import { Delete, Edit } from '@mui/icons-material';
|
||||||
|
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { defaultBorderRadius } from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledBoxCell = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingRight: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IIncomingWebhooksActionsCellProps {
|
||||||
|
incomingWebhookId: number;
|
||||||
|
onCopyToClipboard: (event: React.SyntheticEvent) => void;
|
||||||
|
onEdit: (event: React.SyntheticEvent) => void;
|
||||||
|
onDelete: (event: React.SyntheticEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IncomingWebhooksActionsCell = ({
|
||||||
|
incomingWebhookId,
|
||||||
|
onCopyToClipboard,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
}: IIncomingWebhooksActionsCellProps) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = `incoming-webhook-${incomingWebhookId}-actions`;
|
||||||
|
const menuId = `${id}-menu`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledBoxCell>
|
||||||
|
<Tooltip title='Incoming webhook actions' arrow describeChild>
|
||||||
|
<IconButton
|
||||||
|
id={id}
|
||||||
|
data-loading
|
||||||
|
aria-controls={open ? menuId : undefined}
|
||||||
|
aria-haspopup='true'
|
||||||
|
aria-expanded={open ? 'true' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Popover
|
||||||
|
id={menuId}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
onClick={handleClose}
|
||||||
|
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||||
|
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||||
|
disableScrollLock={true}
|
||||||
|
PaperProps={{
|
||||||
|
sx: (theme) => ({
|
||||||
|
borderRadius: `${theme.shape.borderRadius}px`,
|
||||||
|
padding: theme.spacing(1, 1.5),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuList aria-labelledby={id}>
|
||||||
|
<MenuItem
|
||||||
|
sx={defaultBorderRadius}
|
||||||
|
onClick={onCopyToClipboard}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<FileCopyIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant='body2'>Copy URL</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<PermissionHOC permission={ADMIN}>
|
||||||
|
{({ hasAccess }) => (
|
||||||
|
<MenuItem
|
||||||
|
sx={defaultBorderRadius}
|
||||||
|
onClick={onEdit}
|
||||||
|
disabled={!hasAccess}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Edit />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
Edit
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</PermissionHOC>
|
||||||
|
<PermissionHOC permission={ADMIN}>
|
||||||
|
{({ hasAccess }) => (
|
||||||
|
<MenuItem
|
||||||
|
sx={defaultBorderRadius}
|
||||||
|
onClick={onDelete}
|
||||||
|
disabled={!hasAccess}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Delete />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
Remove
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</PermissionHOC>
|
||||||
|
</MenuList>
|
||||||
|
</Popover>
|
||||||
|
</StyledBoxCell>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||||
|
|
||||||
|
interface IIncomingWebhooksDeleteDialogProps {
|
||||||
|
incomingWebhook?: IIncomingWebhook;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
onConfirm: (incomingWebhook: IIncomingWebhook) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IncomingWebhooksDeleteDialog = ({
|
||||||
|
incomingWebhook,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
onConfirm,
|
||||||
|
}: IIncomingWebhooksDeleteDialogProps) => (
|
||||||
|
<Dialogue
|
||||||
|
title='Delete incoming webhook?'
|
||||||
|
open={open}
|
||||||
|
primaryButtonText='Delete incoming webhook'
|
||||||
|
secondaryButtonText='Cancel'
|
||||||
|
onClick={() => onConfirm(incomingWebhook!)}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You are about to delete incoming webhook:{' '}
|
||||||
|
<strong>{incomingWebhook?.name}</strong>
|
||||||
|
</p>
|
||||||
|
</Dialogue>
|
||||||
|
);
|
@ -0,0 +1,224 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { useMediaQuery } from '@mui/material';
|
||||||
|
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||||
|
import theme from 'themes/theme';
|
||||||
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||||
|
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||||
|
import { useIncomingWebhooksApi } from 'hooks/api/actions/useIncomingWebhooksApi/useIncomingWebhooksApi';
|
||||||
|
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||||
|
import { IncomingWebhooksActionsCell } from './IncomingWebhooksActionsCell';
|
||||||
|
import { IncomingWebhooksDeleteDialog } from './IncomingWebhooksDeleteDialog';
|
||||||
|
import { ToggleCell } from 'component/common/Table/cells/ToggleCell/ToggleCell';
|
||||||
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
// import { IncomingWebhooksModal } from '../IncomingWebhooksModal/IncomingWebhooksModal';
|
||||||
|
|
||||||
|
interface IIncomingWebhooksTableProps {
|
||||||
|
modalOpen: boolean;
|
||||||
|
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
selectedIncomingWebhook?: IIncomingWebhook;
|
||||||
|
setSelectedIncomingWebhook: React.Dispatch<
|
||||||
|
React.SetStateAction<IIncomingWebhook | undefined>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IncomingWebhooksTable = ({
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
selectedIncomingWebhook,
|
||||||
|
setSelectedIncomingWebhook,
|
||||||
|
}: IIncomingWebhooksTableProps) => {
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
|
const { incomingWebhooks, refetch, loading } = useIncomingWebhooks();
|
||||||
|
const { toggleIncomingWebhook, removeIncomingWebhook } =
|
||||||
|
useIncomingWebhooksApi();
|
||||||
|
|
||||||
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
const onToggleIncomingWebhook = async (
|
||||||
|
incomingWebhook: IIncomingWebhook,
|
||||||
|
enabled: boolean,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await toggleIncomingWebhook(incomingWebhook.id, enabled);
|
||||||
|
setToastData({
|
||||||
|
title: `"${incomingWebhook.name}" has been ${
|
||||||
|
enabled ? 'enabled' : 'disabled'
|
||||||
|
}`,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteConfirm = async (incomingWebhook: IIncomingWebhook) => {
|
||||||
|
try {
|
||||||
|
await removeIncomingWebhook(incomingWebhook.id);
|
||||||
|
setToastData({
|
||||||
|
title: `"${incomingWebhook.name}" has been deleted`,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
setDeleteOpen(false);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: incomingWebhook },
|
||||||
|
}: { row: { original: IIncomingWebhook } }) => (
|
||||||
|
<HighlightCell
|
||||||
|
value={incomingWebhook.name}
|
||||||
|
subtitle={incomingWebhook.description}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'URL',
|
||||||
|
accessor: (row: IIncomingWebhook) =>
|
||||||
|
`${uiConfig.unleashUrl}/api/incoming-webhook/${row.name}`,
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Created',
|
||||||
|
accessor: 'createdAt',
|
||||||
|
Cell: DateCell,
|
||||||
|
width: 120,
|
||||||
|
maxWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Enabled',
|
||||||
|
accessor: 'enabled',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: incomingWebhook },
|
||||||
|
}: { row: { original: IIncomingWebhook } }) => (
|
||||||
|
<ToggleCell
|
||||||
|
checked={incomingWebhook.enabled}
|
||||||
|
setChecked={(enabled) =>
|
||||||
|
onToggleIncomingWebhook(incomingWebhook, enabled)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sortType: 'boolean',
|
||||||
|
width: 90,
|
||||||
|
maxWidth: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Actions',
|
||||||
|
id: 'Actions',
|
||||||
|
align: 'center',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: incomingWebhook },
|
||||||
|
}: { row: { original: IIncomingWebhook } }) => (
|
||||||
|
<IncomingWebhooksActionsCell
|
||||||
|
incomingWebhookId={incomingWebhook.id}
|
||||||
|
onCopyToClipboard={() => {
|
||||||
|
copy(
|
||||||
|
`${uiConfig.unleashUrl}/api/incoming-webhook/${incomingWebhook.name}`,
|
||||||
|
);
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Copied to clipboard',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onEdit={() => {
|
||||||
|
setSelectedIncomingWebhook(incomingWebhook);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
setSelectedIncomingWebhook(incomingWebhook);
|
||||||
|
setDeleteOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
width: 100,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [initialState] = useState({
|
||||||
|
sortBy: [{ id: 'createdAt', desc: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||||
|
{
|
||||||
|
columns: columns as any,
|
||||||
|
data: incomingWebhooks,
|
||||||
|
initialState,
|
||||||
|
sortTypes,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
disableMultiSort: true,
|
||||||
|
defaultColumn: {
|
||||||
|
Cell: TextCell,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useSortBy,
|
||||||
|
useFlexLayout,
|
||||||
|
);
|
||||||
|
|
||||||
|
useConditionallyHiddenColumns(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
condition: isSmallScreen,
|
||||||
|
columns: ['createdAt'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
setHiddenColumns,
|
||||||
|
columns,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VirtualizedTable
|
||||||
|
rows={rows}
|
||||||
|
headerGroups={headerGroups}
|
||||||
|
prepareRow={prepareRow}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={rows.length === 0}
|
||||||
|
show={
|
||||||
|
<TablePlaceholder>
|
||||||
|
No incoming webhooks available. Get started by adding
|
||||||
|
one.
|
||||||
|
</TablePlaceholder>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* <IncomingWebhooksModal
|
||||||
|
incomingWebhook={selectedIncomingWebhook}
|
||||||
|
open={modalOpen}
|
||||||
|
setOpen={setModalOpen}
|
||||||
|
/> */}
|
||||||
|
<IncomingWebhooksDeleteDialog
|
||||||
|
incomingWebhook={selectedIncomingWebhook}
|
||||||
|
open={deleteOpen}
|
||||||
|
setOpen={setDeleteOpen}
|
||||||
|
onConfirm={onDeleteConfirm}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { VFC } from 'react';
|
import { VFC, useState } from 'react';
|
||||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||||
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations';
|
||||||
import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations';
|
import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations';
|
||||||
@ -13,6 +13,8 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|||||||
import { TabLink } from 'component/common/TabNav/TabLink';
|
import { TabLink } from 'component/common/TabNav/TabLink';
|
||||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||||
|
import { IncomingWebhooks } from 'component/incomingWebhooks/IncomingWebhooks';
|
||||||
|
|
||||||
const StyledHeader = styled('div')(() => ({
|
const StyledHeader = styled('div')(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -37,11 +39,15 @@ export const IntegrationList: VFC = () => {
|
|||||||
const { providers, addons, loading } = useAddons();
|
const { providers, addons, loading } = useAddons();
|
||||||
const { incomingWebhooks } = useIncomingWebhooks();
|
const { incomingWebhooks } = useIncomingWebhooks();
|
||||||
|
|
||||||
|
const [selectedIncomingWebhook, setSelectedIncomingWebhook] =
|
||||||
|
useState<IIncomingWebhook>();
|
||||||
|
const [incomingWebhookModalOpen, setIncomingWebhookModalOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const onNewIncomingWebhook = () => {
|
const onNewIncomingWebhook = () => {
|
||||||
navigate('/integrations/incoming-webhooks');
|
navigate('/integrations/incoming-webhooks');
|
||||||
// TODO: Implement:
|
setSelectedIncomingWebhook(undefined);
|
||||||
// setSelectedIncomingWebhook(undefined);
|
setIncomingWebhookModalOpen(true);
|
||||||
// setIncomingWebhookModalOpen(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@ -114,7 +120,16 @@ export const IntegrationList: VFC = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path='incoming-webhooks'
|
path='incoming-webhooks'
|
||||||
element={<span>TODO: Implement</span>}
|
element={
|
||||||
|
<IncomingWebhooks
|
||||||
|
modalOpen={incomingWebhookModalOpen}
|
||||||
|
setModalOpen={setIncomingWebhookModalOpen}
|
||||||
|
selectedIncomingWebhook={selectedIncomingWebhook}
|
||||||
|
setSelectedIncomingWebhook={
|
||||||
|
setSelectedIncomingWebhook
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='*'
|
path='*'
|
||||||
|
@ -4,6 +4,7 @@ export interface IIncomingWebhook {
|
|||||||
name: string;
|
name: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
createdByUserId: number;
|
createdByUserId: number;
|
||||||
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIncomingWebhookToken {
|
export interface IIncomingWebhookToken {
|
||||||
|
Loading…
Reference in New Issue
Block a user