2 changed files with 399 additions and 334 deletions
@ -1,196 +1,228 @@
@@ -1,196 +1,228 @@
|
||||
import React, { useState, useEffect } from "react"; |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip } from 'antd'; |
||||
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons'; |
||||
const { Title, Paragraph, Text } = Typography; |
||||
|
||||
import format from 'date-fns/format' |
||||
import format from 'date-fns/format'; |
||||
|
||||
import { |
||||
fetchData, |
||||
ACCESS_TOKENS, |
||||
DELETE_ACCESS_TOKEN, |
||||
CREATE_ACCESS_TOKEN, |
||||
} from "../utils/apis"; |
||||
import { fetchData, ACCESS_TOKENS, DELETE_ACCESS_TOKEN, CREATE_ACCESS_TOKEN } from '../utils/apis'; |
||||
|
||||
const availableScopes = { |
||||
'CAN_SEND_SYSTEM_MESSAGES': { name: 'System messages', description: 'You can send official messages on behalf of the system', color: 'purple' }, |
||||
'CAN_SEND_MESSAGES': { name: 'User chat messages', description: 'You can send messages on behalf of a username', color: 'green' }, |
||||
'HAS_ADMIN_ACCESS': { name: 'Has admin access', description: 'Can perform administrative actions such as moderation, get server statuses, etc', color: 'red' }, |
||||
CAN_SEND_SYSTEM_MESSAGES: { |
||||
name: 'System messages', |
||||
description: 'You can send official messages on behalf of the system', |
||||
color: 'purple', |
||||
}, |
||||
CAN_SEND_MESSAGES: { |
||||
name: 'User chat messages', |
||||
description: 'You can send messages on behalf of a username', |
||||
color: 'green', |
||||
}, |
||||
HAS_ADMIN_ACCESS: { |
||||
name: 'Has admin access', |
||||
description: 'Can perform administrative actions such as moderation, get server statuses, etc', |
||||
color: 'red', |
||||
}, |
||||
}; |
||||
|
||||
function convertScopeStringToTag(scopeString) { |
||||
if (!scopeString || !availableScopes[scopeString]) { |
||||
return null; |
||||
} |
||||
if (!scopeString || !availableScopes[scopeString]) { |
||||
return null; |
||||
} |
||||
|
||||
const scope = availableScopes[scopeString]; |
||||
const scope = availableScopes[scopeString]; |
||||
|
||||
return ( |
||||
<Tooltip key={scopeString} title={scope.description}> |
||||
<Tag color={scope.color} > |
||||
{scope.name} |
||||
</Tag> |
||||
</Tooltip> |
||||
); |
||||
return ( |
||||
<Tooltip key={scopeString} title={scope.description}> |
||||
<Tag color={scope.color}>{scope.name}</Tag> |
||||
</Tooltip> |
||||
); |
||||
} |
||||
|
||||
function NewTokenModal(props) { |
||||
const [selectedScopes, setSelectedScopes] = useState([]); |
||||
const [name, setName] = useState(''); |
||||
|
||||
const scopes = Object.keys(availableScopes).map(function (key) { |
||||
return { value: key, label: availableScopes[key].description } |
||||
}); |
||||
|
||||
function onChange(checkedValues) { |
||||
setSelectedScopes(checkedValues); |
||||
} |
||||
|
||||
function saveToken() { |
||||
props.onOk(name, selectedScopes); |
||||
|
||||
// Clear the modal
|
||||
setSelectedScopes([]); |
||||
setName(''); |
||||
} |
||||
|
||||
const okButtonProps = { |
||||
disabled: selectedScopes.length === 0 || name === '' |
||||
}; |
||||
|
||||
function selectAll() { |
||||
setSelectedScopes(Object.keys(availableScopes)); |
||||
} |
||||
|
||||
return ( |
||||
<Modal title="Create New Access token" visible={props.visible} onOk={saveToken} onCancel={props.onCancel} okButtonProps={okButtonProps}> |
||||
<p><Input value={name} placeholder="Access token name/description" onChange={(input) => setName(input.currentTarget.value)} /></p> |
||||
|
||||
<p> |
||||
Select the permissions this access token will have. It cannot be edited after it's created. |
||||
</p> |
||||
<Checkbox.Group options={scopes} value={selectedScopes} onChange={onChange} /> |
||||
<Button onClick={selectAll}>Select all</Button> |
||||
</Modal> |
||||
) |
||||
const [selectedScopes, setSelectedScopes] = useState([]); |
||||
const [name, setName] = useState(''); |
||||
|
||||
const scopes = Object.keys(availableScopes).map(function (key) { |
||||
return { value: key, label: availableScopes[key].description }; |
||||
}); |
||||
|
||||
function onChange(checkedValues) { |
||||
setSelectedScopes(checkedValues); |
||||
} |
||||
|
||||
function saveToken() { |
||||
props.onOk(name, selectedScopes); |
||||
|
||||
// Clear the modal
|
||||
setSelectedScopes([]); |
||||
setName(''); |
||||
} |
||||
|
||||
const okButtonProps = { |
||||
disabled: selectedScopes.length === 0 || name === '', |
||||
}; |
||||
|
||||
function selectAll() { |
||||
setSelectedScopes(Object.keys(availableScopes)); |
||||
} |
||||
|
||||
return ( |
||||
<Modal |
||||
title="Create New Access token" |
||||
visible={props.visible} |
||||
onOk={saveToken} |
||||
onCancel={props.onCancel} |
||||
okButtonProps={okButtonProps} |
||||
> |
||||
<p> |
||||
<Input |
||||
value={name} |
||||
placeholder="Access token name/description" |
||||
onChange={input => setName(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
|
||||
<p> |
||||
Select the permissions this access token will have. It cannot be edited after it's created. |
||||
</p> |
||||
<Checkbox.Group options={scopes} value={selectedScopes} onChange={onChange} /> |
||||
<Button type="text" size="small" onClick={selectAll}> |
||||
Select all |
||||
</Button> |
||||
</Modal> |
||||
); |
||||
} |
||||
|
||||
export default function AccessTokens() { |
||||
const [tokens, setTokens] = useState([]); |
||||
const [isTokenModalVisible, setIsTokenModalVisible] = useState(false); |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: '', |
||||
key: 'delete', |
||||
render: (text, record) => ( |
||||
<Space size="middle"> |
||||
<Button onClick={() => handleDeleteToken(record.token)} icon={<DeleteOutlined />} /> |
||||
</Space> |
||||
) |
||||
}, |
||||
{ |
||||
title: 'Name', |
||||
dataIndex: 'name', |
||||
key: 'name', |
||||
}, |
||||
{ |
||||
title: 'Token', |
||||
dataIndex: 'token', |
||||
key: 'token', |
||||
render: (text, record) => ( |
||||
<Input.Password size="small" bordered={false} value={text} /> |
||||
) |
||||
}, |
||||
{ |
||||
title: 'Scopes', |
||||
dataIndex: 'scopes', |
||||
key: 'scopes', |
||||
render: scopes => ( |
||||
<> |
||||
{scopes.map(scope => { |
||||
return convertScopeStringToTag(scope); |
||||
})} |
||||
</> |
||||
), |
||||
}, |
||||
{ |
||||
title: 'Last Used', |
||||
dataIndex: 'lastUsed', |
||||
key: 'lastUsed', |
||||
render: (lastUsed) => { |
||||
if (!lastUsed) { |
||||
return 'Never'; |
||||
} |
||||
const dateObject = new Date(lastUsed); |
||||
return format(dateObject, 'P p'); |
||||
}, |
||||
}, |
||||
]; |
||||
|
||||
const getAccessTokens = async () => { |
||||
try { |
||||
const result = await fetchData(ACCESS_TOKENS); |
||||
setTokens(result); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
getAccessTokens(); |
||||
}, []); |
||||
|
||||
async function handleDeleteToken(token) { |
||||
try { |
||||
const result = await fetchData(DELETE_ACCESS_TOKEN, { method: 'POST', data: { token: token } }); |
||||
getAccessTokens(); |
||||
} catch (error) { |
||||
handleError(error); |
||||
const [tokens, setTokens] = useState([]); |
||||
const [isTokenModalVisible, setIsTokenModalVisible] = useState(false); |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: '', |
||||
key: 'delete', |
||||
render: (text, record) => ( |
||||
<Space size="middle"> |
||||
<Button onClick={() => handleDeleteToken(record.token)} icon={<DeleteOutlined />} /> |
||||
</Space> |
||||
), |
||||
}, |
||||
{ |
||||
title: 'Name', |
||||
dataIndex: 'name', |
||||
key: 'name', |
||||
}, |
||||
{ |
||||
title: 'Token', |
||||
dataIndex: 'token', |
||||
key: 'token', |
||||
render: (text, record) => <Input.Password size="small" bordered={false} value={text} />, |
||||
}, |
||||
{ |
||||
title: 'Scopes', |
||||
dataIndex: 'scopes', |
||||
key: 'scopes', |
||||
render: scopes => ( |
||||
<> |
||||
{scopes.map(scope => { |
||||
return convertScopeStringToTag(scope); |
||||
})} |
||||
</> |
||||
), |
||||
}, |
||||
{ |
||||
title: 'Last Used', |
||||
dataIndex: 'lastUsed', |
||||
key: 'lastUsed', |
||||
render: lastUsed => { |
||||
if (!lastUsed) { |
||||
return 'Never'; |
||||
} |
||||
const dateObject = new Date(lastUsed); |
||||
return format(dateObject, 'P p'); |
||||
}, |
||||
}, |
||||
]; |
||||
|
||||
const getAccessTokens = async () => { |
||||
try { |
||||
const result = await fetchData(ACCESS_TOKENS); |
||||
setTokens(result); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
async function handleSaveToken(name: string, scopes: string[]) { |
||||
try { |
||||
const newToken = await fetchData(CREATE_ACCESS_TOKEN, { method: 'POST', data: { name: name, scopes: scopes } }); |
||||
setTokens(tokens.concat(newToken)); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
getAccessTokens(); |
||||
}, []); |
||||
|
||||
async function handleDeleteToken(token) { |
||||
try { |
||||
const result = await fetchData(DELETE_ACCESS_TOKEN, { |
||||
method: 'POST', |
||||
data: { token: token }, |
||||
}); |
||||
getAccessTokens(); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
function handleError(error) { |
||||
console.error("error", error); |
||||
alert(error); |
||||
} |
||||
|
||||
async function handleSaveToken(name: string, scopes: string[]) { |
||||
try { |
||||
const newToken = await fetchData(CREATE_ACCESS_TOKEN, { |
||||
method: 'POST', |
||||
data: { name: name, scopes: scopes }, |
||||
}); |
||||
setTokens(tokens.concat(newToken)); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
const showCreateTokenModal = () => { |
||||
setIsTokenModalVisible(true); |
||||
}; |
||||
|
||||
const handleTokenModalSaveButton = (name, scopes) => { |
||||
setIsTokenModalVisible(false); |
||||
handleSaveToken(name, scopes); |
||||
}; |
||||
|
||||
const handleTokenModalCancel = () => { |
||||
setIsTokenModalVisible(false); |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<Title>Access Tokens</Title> |
||||
<Paragraph> |
||||
Access tokens are used to allow external, 3rd party tools to perform specific actions on your Owncast server. |
||||
They should be kept secure and never included in client code, instead they should be kept on a server that you control. |
||||
</Paragraph> |
||||
<Paragraph> |
||||
Read more about how to use these tokens, with examples, at <a href="https://owncast.online/docs/integrations/">our documentation</a>. |
||||
</Paragraph> |
||||
|
||||
<Table rowKey="token" columns={columns} dataSource={tokens} pagination={false} /> |
||||
<Button onClick={showCreateTokenModal}>Create Access Token</Button> |
||||
<NewTokenModal visible={isTokenModalVisible} onOk={handleTokenModalSaveButton} onCancel={handleTokenModalCancel} /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
function handleError(error) { |
||||
console.error('error', error); |
||||
alert(error); |
||||
} |
||||
|
||||
const showCreateTokenModal = () => { |
||||
setIsTokenModalVisible(true); |
||||
}; |
||||
|
||||
const handleTokenModalSaveButton = (name, scopes) => { |
||||
setIsTokenModalVisible(false); |
||||
handleSaveToken(name, scopes); |
||||
}; |
||||
|
||||
const handleTokenModalCancel = () => { |
||||
setIsTokenModalVisible(false); |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<Title>Access Tokens</Title> |
||||
<Paragraph> |
||||
Access tokens are used to allow external, 3rd party tools to perform specific actions on |
||||
your Owncast server. They should be kept secure and never included in client code, instead |
||||
they should be kept on a server that you control. |
||||
</Paragraph> |
||||
<Paragraph> |
||||
Read more about how to use these tokens, with examples, at{' '} |
||||
<a href="https://owncast.online/docs/integrations/">our documentation</a>. |
||||
</Paragraph> |
||||
|
||||
<Table rowKey="token" columns={columns} dataSource={tokens} pagination={false} /> |
||||
<br /> |
||||
<Button type="primary" onClick={showCreateTokenModal}> |
||||
Create Access Token |
||||
</Button> |
||||
<NewTokenModal |
||||
visible={isTokenModalVisible} |
||||
onOk={handleTokenModalSaveButton} |
||||
onCancel={handleTokenModalCancel} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
@ -1,183 +1,216 @@
@@ -1,183 +1,216 @@
|
||||
import React, { useState, useEffect } from "react"; |
||||
import { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip, Select } from 'antd'; |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { |
||||
Table, |
||||
Tag, |
||||
Space, |
||||
Button, |
||||
Modal, |
||||
Checkbox, |
||||
Input, |
||||
Typography, |
||||
Tooltip, |
||||
Select, |
||||
} from 'antd'; |
||||
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons'; |
||||
import {isValidUrl} from '../utils/urls'; |
||||
import { isValidUrl } from '../utils/urls'; |
||||
|
||||
const { Title, Paragraph, Text } = Typography; |
||||
const { Option } = Select; |
||||
|
||||
import format from 'date-fns/format' |
||||
|
||||
import { |
||||
fetchData, |
||||
DELETE_WEBHOOK, |
||||
CREATE_WEBHOOK, |
||||
WEBHOOKS, |
||||
} from "../utils/apis"; |
||||
import { fetchData, DELETE_WEBHOOK, CREATE_WEBHOOK, WEBHOOKS } from '../utils/apis'; |
||||
|
||||
const availableEvents = { |
||||
'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'} |
||||
|
||||
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' }, |
||||
}; |
||||
|
||||
function convertEventStringToTag(eventString) { |
||||
if (!eventString || !availableEvents[eventString]) { |
||||
return null; |
||||
} |
||||
if (!eventString || !availableEvents[eventString]) { |
||||
return null; |
||||
} |
||||
|
||||
const event = availableEvents[eventString]; |
||||
const event = availableEvents[eventString]; |
||||
|
||||
return ( |
||||
<Tooltip key={eventString} title={event.description}> |
||||
<Tag color={event.color} > |
||||
{event.name} |
||||
</Tag> |
||||
</Tooltip> |
||||
); |
||||
return ( |
||||
<Tooltip key={eventString} title={event.description}> |
||||
<Tag color={event.color}>{event.name}</Tag> |
||||
</Tooltip> |
||||
); |
||||
} |
||||
|
||||
function NewWebhookModal(props) { |
||||
const [selectedEvents, setSelectedEvents] = useState([]); |
||||
const [webhookUrl, setWebhookUrl] = useState(''); |
||||
|
||||
const events = Object.keys(availableEvents).map(function (key) { |
||||
return { value: key, label: availableEvents[key].description } |
||||
}); |
||||
|
||||
|
||||
function onChange(checkedValues) { |
||||
setSelectedEvents(checkedValues); |
||||
} |
||||
|
||||
function selectAll() { |
||||
setSelectedEvents(Object.keys(availableEvents)); |
||||
} |
||||
|
||||
function save() { |
||||
props.onOk(webhookUrl, selectedEvents) |
||||
|
||||
// Reset the modal
|
||||
setWebhookUrl(''); |
||||
setSelectedEvents(null); |
||||
} |
||||
|
||||
const okButtonProps = { |
||||
disabled: selectedEvents?.length === 0 || !isValidUrl(webhookUrl) |
||||
}; |
||||
|
||||
return ( |
||||
<Modal title="Create New Webhook" visible={props.visible} onOk={save} onCancel={props.onCancel} okButtonProps={okButtonProps}> |
||||
<div><Input value={webhookUrl} placeholder="https://myserver.com/webhook" onChange={(input) => setWebhookUrl(input.currentTarget.value)} /></div> |
||||
|
||||
<p> |
||||
Select the events that will be sent to this webhook. |
||||
</p> |
||||
<Checkbox.Group options={events} value={selectedEvents} onChange={onChange} /> |
||||
<Button onClick={selectAll}>Select all</Button> |
||||
</Modal> |
||||
) |
||||
const [selectedEvents, setSelectedEvents] = useState([]); |
||||
const [webhookUrl, setWebhookUrl] = useState(''); |
||||
|
||||
const events = Object.keys(availableEvents).map(function (key) { |
||||
return { value: key, label: availableEvents[key].description }; |
||||
}); |
||||
|
||||
function onChange(checkedValues) { |
||||
setSelectedEvents(checkedValues); |
||||
} |
||||
|
||||
function selectAll() { |
||||
setSelectedEvents(Object.keys(availableEvents)); |
||||
} |
||||
|
||||
function save() { |
||||
props.onOk(webhookUrl, selectedEvents); |
||||
|
||||
// Reset the modal
|
||||
setWebhookUrl(''); |
||||
setSelectedEvents(null); |
||||
} |
||||
|
||||
const okButtonProps = { |
||||
disabled: selectedEvents?.length === 0 || !isValidUrl(webhookUrl), |
||||
}; |
||||
|
||||
return ( |
||||
<Modal |
||||
title="Create New Webhook" |
||||
visible={props.visible} |
||||
onOk={save} |
||||
onCancel={props.onCancel} |
||||
okButtonProps={okButtonProps} |
||||
> |
||||
<div> |
||||
<Input |
||||
value={webhookUrl} |
||||
placeholder="https://myserver.com/webhook" |
||||
onChange={input => setWebhookUrl(input.currentTarget.value)} |
||||
/> |
||||
</div> |
||||
|
||||
<p>Select the events that will be sent to this webhook.</p> |
||||
<Checkbox.Group options={events} value={selectedEvents} onChange={onChange} /> |
||||
<Button type="text" size="small" onClick={selectAll}> |
||||
Select all |
||||
</Button> |
||||
</Modal> |
||||
); |
||||
} |
||||
|
||||
export default function Webhooks() { |
||||
const [webhooks, setWebhooks] = useState([]); |
||||
const [isModalVisible, setIsModalVisible] = useState(false); |
||||
|
||||
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', |
||||
render: events => ( |
||||
<> |
||||
{events.map(event => { |
||||
return convertEventStringToTag(event); |
||||
})} |
||||
</> |
||||
), |
||||
}, |
||||
]; |
||||
|
||||
const getWebhooks = async () => { |
||||
try { |
||||
const result = await fetchData(WEBHOOKS); |
||||
setWebhooks(result); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
getWebhooks(); |
||||
}, []); |
||||
|
||||
async function handleDelete(id) { |
||||
try { |
||||
const result = await fetchData(DELETE_WEBHOOK, { method: 'POST', data: { id: id } }); |
||||
getWebhooks(); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
const [webhooks, setWebhooks] = useState([]); |
||||
const [isModalVisible, setIsModalVisible] = useState(false); |
||||
|
||||
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', |
||||
render: events => ( |
||||
<> |
||||
{events.map(event => { |
||||
return convertEventStringToTag(event); |
||||
})} |
||||
</> |
||||
), |
||||
}, |
||||
]; |
||||
|
||||
const getWebhooks = async () => { |
||||
try { |
||||
const result = await fetchData(WEBHOOKS); |
||||
setWebhooks(result); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
async function handleSave(url: string, events: string[]) { |
||||
try { |
||||
const newHook = await fetchData(CREATE_WEBHOOK, { method: 'POST', data: { url: url, events: events } }); |
||||
setWebhooks(webhooks.concat(newHook)); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
getWebhooks(); |
||||
}, []); |
||||
|
||||
async function handleDelete(id) { |
||||
try { |
||||
const result = await fetchData(DELETE_WEBHOOK, { method: 'POST', data: { id: id } }); |
||||
getWebhooks(); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
function handleError(error) { |
||||
console.error("error", error); |
||||
alert(error); |
||||
} |
||||
|
||||
async function handleSave(url: string, events: string[]) { |
||||
try { |
||||
const newHook = await fetchData(CREATE_WEBHOOK, { |
||||
method: 'POST', |
||||
data: { url: url, events: events }, |
||||
}); |
||||
setWebhooks(webhooks.concat(newHook)); |
||||
} catch (error) { |
||||
handleError(error); |
||||
} |
||||
|
||||
const showCreateModal = () => { |
||||
setIsModalVisible(true); |
||||
}; |
||||
|
||||
const handleModalSaveButton = (url, events) => { |
||||
setIsModalVisible(false); |
||||
handleSave(url, events); |
||||
}; |
||||
|
||||
const handleModalCancelButton = () => { |
||||
setIsModalVisible(false); |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<Title>Webhooks</Title> |
||||
<Paragraph> |
||||
A webhook is a callback made to an external API in response to an event. These are endpoints that live outside of Owncast and run code who wants to be made aware of events that take place on your server. |
||||
</Paragraph> |
||||
<Paragraph> |
||||
Read more about how to use webhooks, with examples, at <a href="https://owncast.online/docs/integrations/">our documentation</a>. |
||||
</Paragraph> |
||||
|
||||
<Table rowKey="id" columns={columns} dataSource={webhooks} pagination={false} /> |
||||
<Button onClick={showCreateModal}>Create Webhook</Button> |
||||
<NewWebhookModal visible={isModalVisible} onOk={handleModalSaveButton} onCancel={handleModalCancelButton} /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
function handleError(error) { |
||||
console.error('error', error); |
||||
alert(error); |
||||
} |
||||
|
||||
const showCreateModal = () => { |
||||
setIsModalVisible(true); |
||||
}; |
||||
|
||||
const handleModalSaveButton = (url, events) => { |
||||
setIsModalVisible(false); |
||||
handleSave(url, events); |
||||
}; |
||||
|
||||
const handleModalCancelButton = () => { |
||||
setIsModalVisible(false); |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<Title>Webhooks</Title> |
||||
<Paragraph> |
||||
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 |
||||
you've started streaming. |
||||
</Paragraph> |
||||
<Paragraph> |
||||
Read more about how to use webhooks, with examples, at{' '} |
||||
<a href="https://owncast.online/docs/integrations/">our documentation</a>. |
||||
</Paragraph> |
||||
|
||||
<Table rowKey="id" columns={columns} dataSource={webhooks} pagination={false} /> |
||||
<br /> |
||||
<Button type="primary" onClick={showCreateModal}> |
||||
Create Webhook |
||||
</Button> |
||||
<NewWebhookModal |
||||
visible={isModalVisible} |
||||
onOk={handleModalSaveButton} |
||||
onCancel={handleModalCancelButton} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
Loading…
Reference in new issue