2021-07-20 07:02:02 +02:00
|
|
|
/* eslint-disable react/destructuring-assignment */
|
2023-01-11 01:39:12 +01:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Checkbox,
|
|
|
|
Col,
|
|
|
|
Input,
|
|
|
|
Modal,
|
|
|
|
Row,
|
|
|
|
Space,
|
|
|
|
Table,
|
|
|
|
Tag,
|
|
|
|
Typography,
|
|
|
|
Tooltip,
|
|
|
|
} from 'antd';
|
2023-01-16 07:31:36 +01:00
|
|
|
import dynamic from 'next/dynamic';
|
2023-01-16 08:11:44 +01:00
|
|
|
import React, { ReactElement, useEffect, useState } from 'react';
|
2022-04-20 04:57:27 +02:00
|
|
|
import { CREATE_WEBHOOK, DELETE_WEBHOOK, fetchData, WEBHOOKS } from '../../utils/apis';
|
2023-04-16 23:17:13 +02:00
|
|
|
import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators';
|
2021-01-06 01:35:37 +01:00
|
|
|
|
2023-01-16 08:11:44 +01:00
|
|
|
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
|
|
|
|
2021-02-14 10:30:42 +01:00
|
|
|
const { Title, Paragraph } = Typography;
|
|
|
|
|
2023-01-16 07:31:36 +01:00
|
|
|
// Lazy loaded components
|
|
|
|
|
|
|
|
const DeleteOutlined = dynamic(() => import('@ant-design/icons/DeleteOutlined'), {
|
|
|
|
ssr: false,
|
|
|
|
});
|
|
|
|
|
2021-01-06 01:35:37 +01:00
|
|
|
const availableEvents = {
|
2021-02-10 09:17:16 +01:00
|
|
|
CHAT: { name: 'Chat messages', description: 'When a user sends a chat message', color: 'purple' },
|
|
|
|
USER_JOINED: { name: 'User joined', description: 'When a user joins the chat', color: 'green' },
|
|
|
|
NAME_CHANGE: {
|
|
|
|
name: 'User name changed',
|
|
|
|
description: 'When a user changes their name',
|
|
|
|
color: 'blue',
|
|
|
|
},
|
|
|
|
'VISIBILITY-UPDATE': {
|
|
|
|
name: 'Message visibility changed',
|
|
|
|
description: 'When a message visibility changes, likely due to moderation',
|
|
|
|
color: 'red',
|
|
|
|
},
|
|
|
|
STREAM_STARTED: { name: 'Stream started', description: 'When a stream starts', color: 'orange' },
|
|
|
|
STREAM_STOPPED: { name: 'Stream stopped', description: 'When a stream stops', color: 'cyan' },
|
2023-03-28 20:34:00 +02:00
|
|
|
STREAM_TITLE_UPDATED: {
|
|
|
|
name: 'Stream title updated',
|
|
|
|
description: 'When a stream title is changed',
|
|
|
|
color: 'yellow',
|
|
|
|
},
|
2021-01-06 01:35:37 +01:00
|
|
|
};
|
|
|
|
|
2021-07-09 20:42:01 +02:00
|
|
|
function convertEventStringToTag(eventString: string) {
|
2021-02-10 09:17:16 +01:00
|
|
|
if (!eventString || !availableEvents[eventString]) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-01-06 01:35:37 +01:00
|
|
|
|
2021-02-10 09:17:16 +01:00
|
|
|
const event = availableEvents[eventString];
|
2021-01-06 01:35:37 +01:00
|
|
|
|
2021-02-10 09:17:16 +01:00
|
|
|
return (
|
|
|
|
<Tooltip key={eventString} title={event.description}>
|
|
|
|
<Tag color={event.color}>{event.name}</Tag>
|
|
|
|
</Tooltip>
|
|
|
|
);
|
2021-01-06 01:35:37 +01:00
|
|
|
}
|
2021-02-15 09:36:06 +01:00
|
|
|
interface Props {
|
|
|
|
onCancel: () => void;
|
|
|
|
onOk: any; // todo: make better type
|
2022-11-01 22:50:23 +01:00
|
|
|
open: boolean;
|
2021-02-15 09:36:06 +01:00
|
|
|
}
|
|
|
|
|
2022-09-07 09:00:28 +02:00
|
|
|
const NewWebhookModal = (props: Props) => {
|
2022-11-01 22:50:23 +01:00
|
|
|
const { onOk, onCancel, open } = props;
|
2021-01-06 01:35:37 +01:00
|
|
|
|
2021-02-10 09:17:16 +01:00
|
|
|
const [selectedEvents, setSelectedEvents] = useState([]);
|
|
|
|
const [webhookUrl, setWebhookUrl] = useState('');
|
|
|
|
|
2021-07-09 20:42:01 +02:00
|
|
|
const events = Object.keys(availableEvents).map(key => ({
|
|
|
|
value: key,
|
|
|
|
label: availableEvents[key].description,
|
|
|
|
}));
|
2021-02-10 09:17:16 +01:00
|
|
|
|
|
|
|
function onChange(checkedValues) {
|
|
|
|
setSelectedEvents(checkedValues);
|
|
|
|
}
|
|
|
|
|
|
|
|
function selectAll() {
|
|
|
|
setSelectedEvents(Object.keys(availableEvents));
|
|
|
|
}
|
|
|
|
|
|
|
|
function save() {
|
2021-02-15 09:36:06 +01:00
|
|
|
onOk(webhookUrl, selectedEvents);
|
2021-02-10 09:17:16 +01:00
|
|
|
|
|
|
|
// Reset the modal
|
|
|
|
setWebhookUrl('');
|
|
|
|
setSelectedEvents(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
const okButtonProps = {
|
|
|
|
disabled: selectedEvents?.length === 0 || !isValidUrl(webhookUrl),
|
|
|
|
};
|
|
|
|
|
2021-07-09 20:42:01 +02:00
|
|
|
const checkboxes = events.map(singleEvent => (
|
|
|
|
<Col span={8} key={singleEvent.value}>
|
|
|
|
<Checkbox value={singleEvent.value}>{singleEvent.label}</Checkbox>
|
|
|
|
</Col>
|
|
|
|
));
|
2021-04-20 02:59:04 +02:00
|
|
|
|
2021-02-10 09:17:16 +01:00
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
title="Create New Webhook"
|
2022-11-01 22:50:23 +01:00
|
|
|
open={open}
|
2021-02-10 09:17:16 +01:00
|
|
|
onOk={save}
|
2021-02-15 09:36:06 +01:00
|
|
|
onCancel={onCancel}
|
2021-02-10 09:17:16 +01:00
|
|
|
okButtonProps={okButtonProps}
|
|
|
|
>
|
|
|
|
<div>
|
|
|
|
<Input
|
|
|
|
value={webhookUrl}
|
|
|
|
placeholder="https://myserver.com/webhook"
|
2021-05-23 08:27:51 +02:00
|
|
|
onChange={input => setWebhookUrl(input.currentTarget.value.trim())}
|
|
|
|
type="url"
|
|
|
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
2021-02-10 09:17:16 +01:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<p>Select the events that will be sent to this webhook.</p>
|
2021-04-20 02:59:04 +02:00
|
|
|
<Checkbox.Group style={{ width: '100%' }} value={selectedEvents} onChange={onChange}>
|
2021-05-23 08:27:51 +02:00
|
|
|
<Row>{checkboxes}</Row>
|
2021-04-20 02:59:04 +02:00
|
|
|
</Checkbox.Group>
|
2021-02-14 10:30:42 +01:00
|
|
|
<p>
|
|
|
|
<Button type="primary" onClick={selectAll}>
|
|
|
|
Select all
|
|
|
|
</Button>
|
|
|
|
</p>
|
2021-02-10 09:17:16 +01:00
|
|
|
</Modal>
|
|
|
|
);
|
2022-09-07 09:00:28 +02:00
|
|
|
};
|
2021-01-06 01:35:37 +01:00
|
|
|
|
2022-09-07 09:00:28 +02:00
|
|
|
const Webhooks = () => {
|
2021-02-10 09:17:16 +01:00
|
|
|
const [webhooks, setWebhooks] = useState([]);
|
2022-11-01 22:50:23 +01:00
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
2021-02-10 09:17:16 +01:00
|
|
|
|
2021-02-15 09:36:06 +01:00
|
|
|
function handleError(error) {
|
|
|
|
console.error('error', error);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getWebhooks() {
|
2021-02-10 09:17:16 +01:00
|
|
|
try {
|
|
|
|
const result = await fetchData(WEBHOOKS);
|
|
|
|
setWebhooks(result);
|
|
|
|
} catch (error) {
|
|
|
|
handleError(error);
|
2021-01-06 01:35:37 +01:00
|
|
|
}
|
2021-02-15 09:36:06 +01:00
|
|
|
}
|
2021-02-10 09:17:16 +01:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
getWebhooks();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
async function handleDelete(id) {
|
|
|
|
try {
|
2021-02-15 09:36:06 +01:00
|
|
|
await fetchData(DELETE_WEBHOOK, { method: 'POST', data: { id } });
|
2021-02-10 09:17:16 +01:00
|
|
|
getWebhooks();
|
|
|
|
} catch (error) {
|
|
|
|
handleError(error);
|
2021-01-06 01:35:37 +01:00
|
|
|
}
|
2021-02-10 09:17:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function handleSave(url: string, events: string[]) {
|
|
|
|
try {
|
|
|
|
const newHook = await fetchData(CREATE_WEBHOOK, {
|
|
|
|
method: 'POST',
|
2021-02-15 09:36:06 +01:00
|
|
|
data: { url, events },
|
2021-02-10 09:17:16 +01:00
|
|
|
});
|
|
|
|
setWebhooks(webhooks.concat(newHook));
|
|
|
|
} catch (error) {
|
|
|
|
handleError(error);
|
2021-01-06 01:35:37 +01:00
|
|
|
}
|
2021-02-10 09:17:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const showCreateModal = () => {
|
2022-11-01 22:50:23 +01:00
|
|
|
setIsModalOpen(true);
|
2021-02-10 09:17:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleModalSaveButton = (url, events) => {
|
2022-11-01 22:50:23 +01:00
|
|
|
setIsModalOpen(false);
|
2021-02-10 09:17:16 +01:00
|
|
|
handleSave(url, events);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleModalCancelButton = () => {
|
2022-11-01 22:50:23 +01:00
|
|
|
setIsModalOpen(false);
|
2021-02-10 09:17:16 +01:00
|
|
|
};
|
|
|
|
|
2021-07-09 20:42:01 +02:00
|
|
|
const columns = [
|
|
|
|
{
|
|
|
|
title: '',
|
|
|
|
key: 'delete',
|
|
|
|
render: (text, record) => (
|
|
|
|
<Space size="middle">
|
|
|
|
<Button onClick={() => handleDelete(record.id)} icon={<DeleteOutlined />} />
|
|
|
|
</Space>
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'URL',
|
|
|
|
dataIndex: 'url',
|
|
|
|
key: 'url',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Events',
|
|
|
|
dataIndex: 'events',
|
|
|
|
key: 'events',
|
2021-07-20 07:02:02 +02:00
|
|
|
render: events => (
|
|
|
|
<>
|
|
|
|
{
|
|
|
|
// eslint-disable-next-line arrow-body-style
|
|
|
|
events.map(event => {
|
|
|
|
return convertEventStringToTag(event);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</>
|
|
|
|
),
|
2021-07-09 20:42:01 +02:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2021-02-10 09:17:16 +01:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Title>Webhooks</Title>
|
|
|
|
<Paragraph>
|
2021-02-10 09:27:02 +01:00
|
|
|
A webhook is a callback made to an external API in response to an event that takes place
|
|
|
|
within Owncast. This can be used to build chat bots or sending automatic notifications that
|
2021-02-15 09:36:06 +01:00
|
|
|
you've started streaming.
|
2021-02-10 09:17:16 +01:00
|
|
|
</Paragraph>
|
|
|
|
<Paragraph>
|
|
|
|
Read more about how to use webhooks, with examples, at{' '}
|
2021-02-26 21:38:55 +01:00
|
|
|
<a
|
2021-03-04 09:53:50 +01:00
|
|
|
href="https://owncast.online/docs/integrations/?source=admin"
|
2021-02-26 21:38:55 +01:00
|
|
|
target="_blank"
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
>
|
|
|
|
our documentation
|
|
|
|
</a>
|
|
|
|
.
|
2021-02-10 09:17:16 +01:00
|
|
|
</Paragraph>
|
|
|
|
|
2021-05-23 08:27:51 +02:00
|
|
|
<Table
|
|
|
|
rowKey={record => record.id}
|
|
|
|
columns={columns}
|
|
|
|
dataSource={webhooks}
|
|
|
|
pagination={false}
|
|
|
|
/>
|
2021-02-10 09:17:16 +01:00
|
|
|
<br />
|
|
|
|
<Button type="primary" onClick={showCreateModal}>
|
|
|
|
Create Webhook
|
|
|
|
</Button>
|
|
|
|
<NewWebhookModal
|
2022-11-01 22:50:23 +01:00
|
|
|
open={isModalOpen}
|
2021-02-10 09:17:16 +01:00
|
|
|
onOk={handleModalSaveButton}
|
|
|
|
onCancel={handleModalCancelButton}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
2022-09-07 09:00:28 +02:00
|
|
|
};
|
2023-01-16 08:11:44 +01:00
|
|
|
|
|
|
|
Webhooks.getLayout = function getLayout(page: ReactElement) {
|
|
|
|
return <AdminLayout page={page} />;
|
|
|
|
};
|
|
|
|
|
2022-09-07 09:00:28 +02:00
|
|
|
export default Webhooks;
|