Merge branch '0.0.6' of github.com:owncast/owncast-admin into 0.0.6
This commit is contained in:
@@ -1,21 +1,28 @@
|
|||||||
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 { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip } from 'antd';
|
||||||
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
const { Title, Paragraph, Text } = Typography;
|
const { Title, Paragraph, Text } = Typography;
|
||||||
|
|
||||||
import format from 'date-fns/format'
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
import {
|
import { fetchData, ACCESS_TOKENS, DELETE_ACCESS_TOKEN, CREATE_ACCESS_TOKEN } from '../utils/apis';
|
||||||
fetchData,
|
|
||||||
ACCESS_TOKENS,
|
|
||||||
DELETE_ACCESS_TOKEN,
|
|
||||||
CREATE_ACCESS_TOKEN,
|
|
||||||
} from "../utils/apis";
|
|
||||||
|
|
||||||
const availableScopes = {
|
const availableScopes = {
|
||||||
'CAN_SEND_SYSTEM_MESSAGES': { name: 'System messages', description: 'You can send official messages on behalf of the system', color: 'purple' },
|
CAN_SEND_SYSTEM_MESSAGES: {
|
||||||
'CAN_SEND_MESSAGES': { name: 'User chat messages', description: 'You can send messages on behalf of a username', color: 'green' },
|
name: 'System messages',
|
||||||
'HAS_ADMIN_ACCESS': { name: 'Has admin access', description: 'Can perform administrative actions such as moderation, get server statuses, etc', color: 'red' },
|
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) {
|
function convertScopeStringToTag(scopeString) {
|
||||||
@@ -27,9 +34,7 @@ function convertScopeStringToTag(scopeString) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip key={scopeString} title={scope.description}>
|
<Tooltip key={scopeString} title={scope.description}>
|
||||||
<Tag color={scope.color} >
|
<Tag color={scope.color}>{scope.name}</Tag>
|
||||||
{scope.name}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,7 +44,7 @@ function NewTokenModal(props) {
|
|||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
const scopes = Object.keys(availableScopes).map(function (key) {
|
const scopes = Object.keys(availableScopes).map(function (key) {
|
||||||
return { value: key, label: availableScopes[key].description }
|
return { value: key, label: availableScopes[key].description };
|
||||||
});
|
});
|
||||||
|
|
||||||
function onChange(checkedValues) {
|
function onChange(checkedValues) {
|
||||||
@@ -55,7 +60,7 @@ function NewTokenModal(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const okButtonProps = {
|
const okButtonProps = {
|
||||||
disabled: selectedScopes.length === 0 || name === ''
|
disabled: selectedScopes.length === 0 || name === '',
|
||||||
};
|
};
|
||||||
|
|
||||||
function selectAll() {
|
function selectAll() {
|
||||||
@@ -63,16 +68,30 @@ function NewTokenModal(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title="Create New Access token" visible={props.visible} onOk={saveToken} onCancel={props.onCancel} okButtonProps={okButtonProps}>
|
<Modal
|
||||||
<p><Input value={name} placeholder="Access token name/description" onChange={(input) => setName(input.currentTarget.value)} /></p>
|
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>
|
<p>
|
||||||
Select the permissions this access token will have. It cannot be edited after it's created.
|
Select the permissions this access token will have. It cannot be edited after it's created.
|
||||||
</p>
|
</p>
|
||||||
<Checkbox.Group options={scopes} value={selectedScopes} onChange={onChange} />
|
<Checkbox.Group options={scopes} value={selectedScopes} onChange={onChange} />
|
||||||
<Button onClick={selectAll}>Select all</Button>
|
<Button type="text" size="small" onClick={selectAll}>
|
||||||
|
Select all
|
||||||
|
</Button>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AccessTokens() {
|
export default function AccessTokens() {
|
||||||
@@ -87,7 +106,7 @@ export default function AccessTokens() {
|
|||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Button onClick={() => handleDeleteToken(record.token)} icon={<DeleteOutlined />} />
|
<Button onClick={() => handleDeleteToken(record.token)} icon={<DeleteOutlined />} />
|
||||||
</Space>
|
</Space>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
@@ -98,9 +117,7 @@ export default function AccessTokens() {
|
|||||||
title: 'Token',
|
title: 'Token',
|
||||||
dataIndex: 'token',
|
dataIndex: 'token',
|
||||||
key: 'token',
|
key: 'token',
|
||||||
render: (text, record) => (
|
render: (text, record) => <Input.Password size="small" bordered={false} value={text} />,
|
||||||
<Input.Password size="small" bordered={false} value={text} />
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Scopes',
|
title: 'Scopes',
|
||||||
@@ -118,7 +135,7 @@ export default function AccessTokens() {
|
|||||||
title: 'Last Used',
|
title: 'Last Used',
|
||||||
dataIndex: 'lastUsed',
|
dataIndex: 'lastUsed',
|
||||||
key: 'lastUsed',
|
key: 'lastUsed',
|
||||||
render: (lastUsed) => {
|
render: lastUsed => {
|
||||||
if (!lastUsed) {
|
if (!lastUsed) {
|
||||||
return 'Never';
|
return 'Never';
|
||||||
}
|
}
|
||||||
@@ -143,7 +160,10 @@ export default function AccessTokens() {
|
|||||||
|
|
||||||
async function handleDeleteToken(token) {
|
async function handleDeleteToken(token) {
|
||||||
try {
|
try {
|
||||||
const result = await fetchData(DELETE_ACCESS_TOKEN, { method: 'POST', data: { token: token } });
|
const result = await fetchData(DELETE_ACCESS_TOKEN, {
|
||||||
|
method: 'POST',
|
||||||
|
data: { token: token },
|
||||||
|
});
|
||||||
getAccessTokens();
|
getAccessTokens();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
@@ -152,7 +172,10 @@ export default function AccessTokens() {
|
|||||||
|
|
||||||
async function handleSaveToken(name: string, scopes: string[]) {
|
async function handleSaveToken(name: string, scopes: string[]) {
|
||||||
try {
|
try {
|
||||||
const newToken = await fetchData(CREATE_ACCESS_TOKEN, { method: 'POST', data: { name: name, scopes: scopes } });
|
const newToken = await fetchData(CREATE_ACCESS_TOKEN, {
|
||||||
|
method: 'POST',
|
||||||
|
data: { name: name, scopes: scopes },
|
||||||
|
});
|
||||||
setTokens(tokens.concat(newToken));
|
setTokens(tokens.concat(newToken));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
@@ -160,7 +183,7 @@ export default function AccessTokens() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.error("error", error);
|
console.error('error', error);
|
||||||
alert(error);
|
alert(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,16 +204,25 @@ export default function AccessTokens() {
|
|||||||
<div>
|
<div>
|
||||||
<Title>Access Tokens</Title>
|
<Title>Access Tokens</Title>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Access tokens are used to allow external, 3rd party tools to perform specific actions on your Owncast server.
|
Access tokens are used to allow external, 3rd party tools to perform specific actions on
|
||||||
They should be kept secure and never included in client code, instead they should be kept on a server that you control.
|
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>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Read more about how to use these tokens, with examples, at <a href="https://owncast.online/docs/integrations/">our documentation</a>.
|
Read more about how to use these tokens, with examples, at{' '}
|
||||||
|
<a href="https://owncast.online/docs/integrations/">our documentation</a>.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Table rowKey="token" columns={columns} dataSource={tokens} pagination={false} />
|
<Table rowKey="token" columns={columns} dataSource={tokens} pagination={false} />
|
||||||
<Button onClick={showCreateTokenModal}>Create Access Token</Button>
|
<br />
|
||||||
<NewTokenModal visible={isTokenModalVisible} onOk={handleTokenModalSaveButton} onCancel={handleTokenModalCancel} />
|
<Button type="primary" onClick={showCreateTokenModal}>
|
||||||
|
Create Access Token
|
||||||
|
</Button>
|
||||||
|
<NewTokenModal
|
||||||
|
visible={isTokenModalVisible}
|
||||||
|
onOk={handleTokenModalSaveButton}
|
||||||
|
onCancel={handleTokenModalCancel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,39 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip, Select } from 'antd';
|
import {
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Checkbox,
|
||||||
|
Input,
|
||||||
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
Select,
|
||||||
|
} from 'antd';
|
||||||
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
import {isValidUrl} from '../utils/urls';
|
import { isValidUrl } from '../utils/urls';
|
||||||
|
|
||||||
const { Title, Paragraph, Text } = Typography;
|
const { Title, Paragraph, Text } = Typography;
|
||||||
const { Option } = Select;
|
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 = {
|
const availableEvents = {
|
||||||
'CHAT': { name: 'Chat messages', description: 'When a user sends a chat message', color: 'purple' },
|
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'},
|
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'},
|
NAME_CHANGE: {
|
||||||
'VISIBILITY-UPDATE': { name: 'Message visibility changed', description: 'When a message visibility changes, likely due to moderation', color: 'red'},
|
name: 'User name changed',
|
||||||
'STREAM_STARTED': {name: 'Stream started', description: 'When a stream starts', color: 'orange'},
|
description: 'When a user changes their name',
|
||||||
'STREAM_STOPPED': {name: 'Stream stopped', description: 'When a stream stops', color: 'cyan'}
|
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) {
|
function convertEventStringToTag(eventString) {
|
||||||
@@ -34,9 +45,7 @@ function convertEventStringToTag(eventString) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip key={eventString} title={event.description}>
|
<Tooltip key={eventString} title={event.description}>
|
||||||
<Tag color={event.color} >
|
<Tag color={event.color}>{event.name}</Tag>
|
||||||
{event.name}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,10 +55,9 @@ function NewWebhookModal(props) {
|
|||||||
const [webhookUrl, setWebhookUrl] = useState('');
|
const [webhookUrl, setWebhookUrl] = useState('');
|
||||||
|
|
||||||
const events = Object.keys(availableEvents).map(function (key) {
|
const events = Object.keys(availableEvents).map(function (key) {
|
||||||
return { value: key, label: availableEvents[key].description }
|
return { value: key, label: availableEvents[key].description };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function onChange(checkedValues) {
|
function onChange(checkedValues) {
|
||||||
setSelectedEvents(checkedValues);
|
setSelectedEvents(checkedValues);
|
||||||
}
|
}
|
||||||
@@ -59,7 +67,7 @@ function NewWebhookModal(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
props.onOk(webhookUrl, selectedEvents)
|
props.onOk(webhookUrl, selectedEvents);
|
||||||
|
|
||||||
// Reset the modal
|
// Reset the modal
|
||||||
setWebhookUrl('');
|
setWebhookUrl('');
|
||||||
@@ -67,20 +75,32 @@ function NewWebhookModal(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const okButtonProps = {
|
const okButtonProps = {
|
||||||
disabled: selectedEvents?.length === 0 || !isValidUrl(webhookUrl)
|
disabled: selectedEvents?.length === 0 || !isValidUrl(webhookUrl),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title="Create New Webhook" visible={props.visible} onOk={save} onCancel={props.onCancel} okButtonProps={okButtonProps}>
|
<Modal
|
||||||
<div><Input value={webhookUrl} placeholder="https://myserver.com/webhook" onChange={(input) => setWebhookUrl(input.currentTarget.value)} /></div>
|
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>
|
<p>Select the events that will be sent to this webhook.</p>
|
||||||
Select the events that will be sent to this webhook.
|
|
||||||
</p>
|
|
||||||
<Checkbox.Group options={events} value={selectedEvents} onChange={onChange} />
|
<Checkbox.Group options={events} value={selectedEvents} onChange={onChange} />
|
||||||
<Button onClick={selectAll}>Select all</Button>
|
<Button type="text" size="small" onClick={selectAll}>
|
||||||
|
Select all
|
||||||
|
</Button>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Webhooks() {
|
export default function Webhooks() {
|
||||||
@@ -95,7 +115,7 @@ export default function Webhooks() {
|
|||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
<Button onClick={() => handleDelete(record.id)} icon={<DeleteOutlined />} />
|
<Button onClick={() => handleDelete(record.id)} icon={<DeleteOutlined />} />
|
||||||
</Space>
|
</Space>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'URL',
|
title: 'URL',
|
||||||
@@ -140,7 +160,10 @@ export default function Webhooks() {
|
|||||||
|
|
||||||
async function handleSave(url: string, events: string[]) {
|
async function handleSave(url: string, events: string[]) {
|
||||||
try {
|
try {
|
||||||
const newHook = await fetchData(CREATE_WEBHOOK, { method: 'POST', data: { url: url, events: events } });
|
const newHook = await fetchData(CREATE_WEBHOOK, {
|
||||||
|
method: 'POST',
|
||||||
|
data: { url: url, events: events },
|
||||||
|
});
|
||||||
setWebhooks(webhooks.concat(newHook));
|
setWebhooks(webhooks.concat(newHook));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
@@ -148,7 +171,7 @@ export default function Webhooks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.error("error", error);
|
console.error('error', error);
|
||||||
alert(error);
|
alert(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,15 +192,25 @@ export default function Webhooks() {
|
|||||||
<div>
|
<div>
|
||||||
<Title>Webhooks</Title>
|
<Title>Webhooks</Title>
|
||||||
<Paragraph>
|
<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.
|
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>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Read more about how to use webhooks, with examples, at <a href="https://owncast.online/docs/integrations/">our documentation</a>.
|
Read more about how to use webhooks, with examples, at{' '}
|
||||||
|
<a href="https://owncast.online/docs/integrations/">our documentation</a>.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Table rowKey="id" columns={columns} dataSource={webhooks} pagination={false} />
|
<Table rowKey="id" columns={columns} dataSource={webhooks} pagination={false} />
|
||||||
<Button onClick={showCreateModal}>Create Webhook</Button>
|
<br />
|
||||||
<NewWebhookModal visible={isModalVisible} onOk={handleModalSaveButton} onCancel={handleModalCancelButton} />
|
<Button type="primary" onClick={showCreateModal}>
|
||||||
|
Create Webhook
|
||||||
|
</Button>
|
||||||
|
<NewWebhookModal
|
||||||
|
visible={isModalVisible}
|
||||||
|
onOk={handleModalSaveButton}
|
||||||
|
onCancel={handleModalCancelButton}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user