Mange outbound notification settings (#458)
* ActivityPub admin pages for configuration * Fix dev build * Add support for requiring follow approval. Closes https://github.com/owncast/owncast/issues/1208 * Point at admin version of followers endpoint * Add setting for toggling displaying fediverse engagement in admin. https://github.com/owncast/owncast/issues/1404 * Add instance URL textfield to federation config and disable federation if it is empty * If instance URL is not https disable federation * Tweak federation toggle text. Make go live message optional * Add federation info modal. Closes https://github.com/owncast/owncast/issues/1544 * Add support for blocked federated domains. For https://github.com/owncast/owncast/issues/1209 * Simplify fediverse post input * Add placeholder Fediverse icon * Tweak federation logo in admin menu. Closes https://github.com/owncast/owncast/issues/1603 * Add global button for composing a fediverse post. Closes https://github.com/owncast/owncast/issues/1610 * Federation -> Social * Add page for listing federated actions. Closes https://github.com/owncast/owncast/issues/1573 * Auto-close social post modal after success * Make user modal action buttons look nicer * Center and reduce width and center count column. Closes https://github.com/owncast/owncast/issues/1580 * Update the followers table to be clearer * Fix exception thrown when passing undefined * Disable federation settings if feature is disabled * Update enable social modal. For https://github.com/owncast/owncast/issues/1594 * Fix type props * Quiet, linter * Move compose button to the left * Add tooltip for compose button * Add NSFW toggle to federation config. Closes https://github.com/owncast/owncast/issues/1628 * Add support for blocking/removing followers. For https://github.com/owncast/owncast/issues/1630 * Allow editing the server url field even when federation is disabled * Continue to update the copy around the social features * Use relative path to action images. Fixes https://github.com/owncast/owncast/issues/1646 * Link IRIs and make action verbse present tense * Update caniuse * Notifications configuration UI * Remove twilio add email notifications * WIP email support * Add support for managing twitter notifications * Mark fields as passwords * Continued WIP * Post merge cleanup * Cleanup * Remove email config. Handle disabled notifications state * Remove email related components * Remove email related settings properties
This commit is contained in:
129
web/components/config/notification/browser.tsx
Normal file
129
web/components/config/notification/browser.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
|
import TextField, { TEXTFIELD_TYPE_TEXTAREA } from '../form-textfield';
|
||||||
|
import {
|
||||||
|
postConfigUpdateToAPI,
|
||||||
|
RESET_TIMEOUT,
|
||||||
|
BROWSER_PUSH_CONFIG_FIELDS,
|
||||||
|
} from '../../../utils/config-constants';
|
||||||
|
import ToggleSwitch from '../form-toggleswitch';
|
||||||
|
import {
|
||||||
|
createInputStatus,
|
||||||
|
StatusState,
|
||||||
|
STATUS_ERROR,
|
||||||
|
STATUS_SUCCESS,
|
||||||
|
} from '../../../utils/input-statuses';
|
||||||
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
|
import FormStatusIndicator from '../form-status-indicator';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigNotify() {
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
|
const { notifications } = serverConfig || {};
|
||||||
|
const { browser } = notifications || {};
|
||||||
|
|
||||||
|
const { enabled, goLiveMessage } = browser || {};
|
||||||
|
|
||||||
|
const [formDataValues, setFormDataValues] = useState<any>({});
|
||||||
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
|
const [enableSaveButton, setEnableSaveButton] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormDataValues({
|
||||||
|
enabled,
|
||||||
|
goLiveMessage,
|
||||||
|
});
|
||||||
|
}, [notifications, browser]);
|
||||||
|
|
||||||
|
const canSave = (): boolean => true;
|
||||||
|
|
||||||
|
// update individual values in state
|
||||||
|
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
|
||||||
|
console.log(fieldName, value);
|
||||||
|
setFormDataValues({
|
||||||
|
...formDataValues,
|
||||||
|
[fieldName]: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
setEnableSaveButton(canSave());
|
||||||
|
};
|
||||||
|
|
||||||
|
// toggle switch.
|
||||||
|
const handleSwitchChange = (switchEnabled: boolean) => {
|
||||||
|
// setShouldDisplayForm(storageEnabled);
|
||||||
|
handleFieldChange({ fieldName: 'enabled', value: switchEnabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
let resetTimer = null;
|
||||||
|
const resetStates = () => {
|
||||||
|
setSubmitStatus(null);
|
||||||
|
resetTimer = null;
|
||||||
|
clearTimeout(resetTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const postValue = formDataValues;
|
||||||
|
|
||||||
|
await postConfigUpdateToAPI({
|
||||||
|
apiPath: '/notifications/browser',
|
||||||
|
data: { value: postValue },
|
||||||
|
onSuccess: () => {
|
||||||
|
setFieldInConfigState({
|
||||||
|
fieldName: 'browser',
|
||||||
|
value: postValue,
|
||||||
|
path: 'notifications',
|
||||||
|
});
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
onError: (message: string) => {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Browser Alerts</Title>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
Viewers can opt into being notified when you go live with their browser.
|
||||||
|
</p>
|
||||||
|
<p className="description reduced-margins">Not all browsers support this.</p>
|
||||||
|
<ToggleSwitch
|
||||||
|
apiPath=""
|
||||||
|
fieldName="enabled"
|
||||||
|
label="Enable browser notifications"
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
checked={formDataValues.enabled}
|
||||||
|
/>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...BROWSER_PUSH_CONFIG_FIELDS.goLiveMessage}
|
||||||
|
required
|
||||||
|
type={TEXTFIELD_TYPE_TEXTAREA}
|
||||||
|
value={formDataValues.goLiveMessage}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
display: enableSaveButton ? 'inline-block' : 'none',
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
right: '0',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
onClick={save}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
153
web/components/config/notification/discord.tsx
Normal file
153
web/components/config/notification/discord.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
|
import TextField from '../form-textfield';
|
||||||
|
import FormStatusIndicator from '../form-status-indicator';
|
||||||
|
import {
|
||||||
|
postConfigUpdateToAPI,
|
||||||
|
RESET_TIMEOUT,
|
||||||
|
DISCORD_CONFIG_FIELDS,
|
||||||
|
} from '../../../utils/config-constants';
|
||||||
|
import ToggleSwitch from '../form-toggleswitch';
|
||||||
|
import {
|
||||||
|
createInputStatus,
|
||||||
|
StatusState,
|
||||||
|
STATUS_ERROR,
|
||||||
|
STATUS_SUCCESS,
|
||||||
|
} from '../../../utils/input-statuses';
|
||||||
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigNotify() {
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
|
const { notifications } = serverConfig || {};
|
||||||
|
const { discord } = notifications || {};
|
||||||
|
|
||||||
|
const { enabled, webhook, goLiveMessage } = discord || {};
|
||||||
|
|
||||||
|
const [formDataValues, setFormDataValues] = useState<any>({});
|
||||||
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
|
const [enableSaveButton, setEnableSaveButton] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormDataValues({
|
||||||
|
enabled,
|
||||||
|
webhook,
|
||||||
|
goLiveMessage,
|
||||||
|
});
|
||||||
|
}, [notifications, discord]);
|
||||||
|
|
||||||
|
const canSave = (): boolean => {
|
||||||
|
if (webhook === '' || goLiveMessage === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// update individual values in state
|
||||||
|
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
|
||||||
|
setFormDataValues({
|
||||||
|
...formDataValues,
|
||||||
|
[fieldName]: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
setEnableSaveButton(canSave());
|
||||||
|
};
|
||||||
|
|
||||||
|
let resetTimer = null;
|
||||||
|
const resetStates = () => {
|
||||||
|
setSubmitStatus(null);
|
||||||
|
resetTimer = null;
|
||||||
|
clearTimeout(resetTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const postValue = formDataValues;
|
||||||
|
|
||||||
|
await postConfigUpdateToAPI({
|
||||||
|
apiPath: '/notifications/discord',
|
||||||
|
data: { value: postValue },
|
||||||
|
onSuccess: () => {
|
||||||
|
setFieldInConfigState({
|
||||||
|
fieldName: 'discord',
|
||||||
|
value: postValue,
|
||||||
|
path: 'notifications',
|
||||||
|
});
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
onError: (message: string) => {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// toggle switch.
|
||||||
|
const handleSwitchChange = (switchEnabled: boolean) => {
|
||||||
|
// setShouldDisplayForm(storageEnabled);
|
||||||
|
handleFieldChange({ fieldName: 'enabled', value: switchEnabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Discord</Title>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
Let your Discord channel know each time you go live.
|
||||||
|
</p>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
<a
|
||||||
|
href="https://support.discord.com/hc/en-us/articles/228383668"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Create a webhook
|
||||||
|
</a>{' '}
|
||||||
|
under <i>Edit Channel / Integrations</i> on your Discord channel and provide it below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ToggleSwitch
|
||||||
|
apiPath=""
|
||||||
|
fieldName="discordEnabled"
|
||||||
|
label="Enable Discord"
|
||||||
|
checked={formDataValues.enabled}
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
/>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...DISCORD_CONFIG_FIELDS.webhookUrl}
|
||||||
|
required
|
||||||
|
value={formDataValues.webhook}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...DISCORD_CONFIG_FIELDS.goLiveMessage}
|
||||||
|
required
|
||||||
|
value={formDataValues.goLiveMessage}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={save}
|
||||||
|
style={{
|
||||||
|
display: enableSaveButton ? 'inline-block' : 'none',
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
right: '0',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
web/components/config/notification/federation.tsx
Normal file
51
web/components/config/notification/federation.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigNotify() {
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig } = serverStatusData || {};
|
||||||
|
const { federation } = serverConfig || {};
|
||||||
|
|
||||||
|
const { enabled } = federation || {};
|
||||||
|
const [formDataValues, setFormDataValues] = useState<any>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormDataValues({
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
}, [enabled]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Fediverse Social</Title>
|
||||||
|
<p className="description">
|
||||||
|
Enabling the Fediverse social features will not just alert people to when you go live, but
|
||||||
|
also enable other functionality.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Fediverse social features:{' '}
|
||||||
|
<span style={{ color: federation.enabled ? 'green' : 'red' }}>
|
||||||
|
{formDataValues.enabled ? 'Enabled' : 'Disabled'}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link passHref href="/config-federation">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
right: '0',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Configure
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
198
web/components/config/notification/twitter.tsx
Normal file
198
web/components/config/notification/twitter.tsx
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
|
import TextField, { TEXTFIELD_TYPE_PASSWORD } from '../form-textfield';
|
||||||
|
import FormStatusIndicator from '../form-status-indicator';
|
||||||
|
import {
|
||||||
|
postConfigUpdateToAPI,
|
||||||
|
RESET_TIMEOUT,
|
||||||
|
TWITTER_CONFIG_FIELDS,
|
||||||
|
} from '../../../utils/config-constants';
|
||||||
|
import ToggleSwitch from '../form-toggleswitch';
|
||||||
|
import {
|
||||||
|
createInputStatus,
|
||||||
|
StatusState,
|
||||||
|
STATUS_ERROR,
|
||||||
|
STATUS_SUCCESS,
|
||||||
|
} from '../../../utils/input-statuses';
|
||||||
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
|
import { TEXTFIELD_TYPE_TEXT } from '../form-textfield-with-submit';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigNotify() {
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
|
const { notifications } = serverConfig || {};
|
||||||
|
const { twitter } = notifications || {};
|
||||||
|
|
||||||
|
const { enabled, apiKey, apiSecret, accessToken, accessTokenSecret, bearerToken, goLiveMessage } =
|
||||||
|
twitter || {};
|
||||||
|
|
||||||
|
const [formDataValues, setFormDataValues] = useState<any>({});
|
||||||
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
|
const [enableSaveButton, setEnableSaveButton] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormDataValues({
|
||||||
|
enabled,
|
||||||
|
apiKey,
|
||||||
|
apiSecret,
|
||||||
|
accessToken,
|
||||||
|
accessTokenSecret,
|
||||||
|
bearerToken,
|
||||||
|
goLiveMessage,
|
||||||
|
});
|
||||||
|
}, [twitter]);
|
||||||
|
|
||||||
|
const canSave = (): boolean => true;
|
||||||
|
|
||||||
|
// update individual values in state
|
||||||
|
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
|
||||||
|
setFormDataValues({
|
||||||
|
...formDataValues,
|
||||||
|
[fieldName]: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
setEnableSaveButton(canSave());
|
||||||
|
};
|
||||||
|
|
||||||
|
// toggle switch.
|
||||||
|
const handleSwitchChange = (switchEnabled: boolean) => {
|
||||||
|
handleFieldChange({ fieldName: 'enabled', value: switchEnabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
let resetTimer = null;
|
||||||
|
const resetStates = () => {
|
||||||
|
setSubmitStatus(null);
|
||||||
|
resetTimer = null;
|
||||||
|
clearTimeout(resetTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const postValue = formDataValues;
|
||||||
|
|
||||||
|
await postConfigUpdateToAPI({
|
||||||
|
apiPath: '/notifications/twitter',
|
||||||
|
data: { value: postValue },
|
||||||
|
onSuccess: () => {
|
||||||
|
setFieldInConfigState({
|
||||||
|
fieldName: 'twitter',
|
||||||
|
value: postValue,
|
||||||
|
path: 'notifications',
|
||||||
|
});
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
onError: (message: string) => {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Twitter</Title>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
Let your Twitter followers know each time you go live.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
<a
|
||||||
|
href="https://developer.twitter.com/en/portal/dashboard"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Read how to configure your Twitter account
|
||||||
|
</a>{' '}
|
||||||
|
to support posting from Owncast.
|
||||||
|
</p>
|
||||||
|
<p className="description reduced-margins">
|
||||||
|
<a
|
||||||
|
href="https://developer.twitter.com/en/portal/dashboard"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
And then get your Twitter developer credentials
|
||||||
|
</a>{' '}
|
||||||
|
to fill in below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ToggleSwitch
|
||||||
|
apiPath=""
|
||||||
|
fieldName="enabled"
|
||||||
|
label="Enable Twitter"
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
checked={formDataValues.enabled}
|
||||||
|
/>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.apiKey}
|
||||||
|
required
|
||||||
|
value={formDataValues.apiKey}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.apiSecret}
|
||||||
|
type={TEXTFIELD_TYPE_PASSWORD}
|
||||||
|
required
|
||||||
|
value={formDataValues.apiSecret}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.accessToken}
|
||||||
|
required
|
||||||
|
value={formDataValues.accessToken}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.accessTokenSecret}
|
||||||
|
type={TEXTFIELD_TYPE_PASSWORD}
|
||||||
|
required
|
||||||
|
value={formDataValues.accessTokenSecret}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.bearerToken}
|
||||||
|
required
|
||||||
|
value={formDataValues.bearerToken}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: formDataValues.enabled ? 'block' : 'none' }}>
|
||||||
|
<TextField
|
||||||
|
{...TWITTER_CONFIG_FIELDS.goLiveMessage}
|
||||||
|
type={TEXTFIELD_TYPE_TEXT}
|
||||||
|
required
|
||||||
|
value={formDataValues.goLiveMessage}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={save}
|
||||||
|
style={{
|
||||||
|
display: enableSaveButton ? 'inline-block' : 'none',
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
right: '0',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -196,6 +196,9 @@ export default function MainLayout(props) {
|
|||||||
<Menu.Item key="config-federation">
|
<Menu.Item key="config-federation">
|
||||||
<Link href="/config-federation">Social</Link>
|
<Link href="/config-federation">Social</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item key="config-notify">
|
||||||
|
<Link href="/config-notify">Notifications</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item key="config-storage">
|
<Menu.Item key="config-storage">
|
||||||
<Link href="/config-storage">S3 Storage</Link>
|
<Link href="/config-storage">S3 Storage</Link>
|
||||||
|
|||||||
135
web/pages/config-notify.tsx
Normal file
135
web/pages/config-notify.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { Alert, Button, Col, Row, Typography } from 'antd';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import Discord from '../components/config/notification/discord';
|
||||||
|
import Browser from '../components/config/notification/browser';
|
||||||
|
import Twitter from '../components/config/notification/twitter';
|
||||||
|
import Federation from '../components/config/notification/federation';
|
||||||
|
import TextFieldWithSubmit, {
|
||||||
|
TEXTFIELD_TYPE_URL,
|
||||||
|
} from '../components/config/form-textfield-with-submit';
|
||||||
|
import { TEXTFIELD_PROPS_FEDERATION_INSTANCE_URL } from '../utils/config-constants';
|
||||||
|
import { ServerStatusContext } from '../utils/server-status-context';
|
||||||
|
import { UpdateArgs } from '../types/config-section';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigNotify() {
|
||||||
|
const [formDataValues, setFormDataValues] = useState(null);
|
||||||
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
|
const { serverConfig } = serverStatusData || {};
|
||||||
|
const { yp } = serverConfig;
|
||||||
|
const { instanceUrl } = yp;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormDataValues({
|
||||||
|
instanceUrl,
|
||||||
|
});
|
||||||
|
}, [yp]);
|
||||||
|
|
||||||
|
const handleSubmitInstanceUrl = () => {
|
||||||
|
setFormDataValues({
|
||||||
|
...formDataValues,
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
|
||||||
|
setFormDataValues({
|
||||||
|
...formDataValues,
|
||||||
|
[fieldName]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const enabled = instanceUrl !== '';
|
||||||
|
console.log(enabled);
|
||||||
|
const configurationWarning = !enabled && (
|
||||||
|
<>
|
||||||
|
<Alert
|
||||||
|
message="You must set your server URL before you can enable this feature."
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextFieldWithSubmit
|
||||||
|
fieldName="instanceUrl"
|
||||||
|
{...TEXTFIELD_PROPS_FEDERATION_INSTANCE_URL}
|
||||||
|
value={formDataValues?.instanceUrl || ''}
|
||||||
|
initialValue={yp.instanceUrl}
|
||||||
|
type={TEXTFIELD_TYPE_URL}
|
||||||
|
onChange={handleFieldChange}
|
||||||
|
onSubmit={handleSubmitInstanceUrl}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Notifications</Title>
|
||||||
|
<p className="description">
|
||||||
|
Let your viewers know when you go live by supporting some of the following notification
|
||||||
|
channels.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{configurationWarning}
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col
|
||||||
|
span={10}
|
||||||
|
className={`form-module ${enabled ? '' : 'disabled'}`}
|
||||||
|
style={{ margin: '5px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Browser />
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
span={10}
|
||||||
|
className={`form-module ${enabled ? '' : 'disabled'}`}
|
||||||
|
style={{ margin: '5px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Twitter />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col
|
||||||
|
span={10}
|
||||||
|
className={`form-module ${enabled ? '' : 'disabled'}`}
|
||||||
|
style={{ margin: '5px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Discord />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col
|
||||||
|
span={10}
|
||||||
|
className={`form-module ${enabled ? '' : 'disabled'}`}
|
||||||
|
style={{ margin: '5px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Federation />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col
|
||||||
|
span={10}
|
||||||
|
className={`form-module ${enabled ? '' : 'disabled'}`}
|
||||||
|
style={{ margin: '5px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Title>Custom</Title>
|
||||||
|
<p className="description">Build your own notifications by using custom webhooks.</p>
|
||||||
|
|
||||||
|
<Link passHref href="/webhooks">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
right: '0',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -36,6 +36,12 @@ p.description,
|
|||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
color: var(--white-75);
|
color: var(--white-75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description.reduced-margins {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -86,6 +92,11 @@ strong {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-module.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -98,6 +98,33 @@ export interface Federation {
|
|||||||
blockedDomains: string[];
|
blockedDomains: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BrowserNotification {
|
||||||
|
enabled: boolean;
|
||||||
|
goLiveMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiscordNotification {
|
||||||
|
enabled: boolean;
|
||||||
|
webhook: string;
|
||||||
|
goLiveMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TwitterNotification {
|
||||||
|
enabled: boolean;
|
||||||
|
apiKey: string;
|
||||||
|
apiSecret: string;
|
||||||
|
accessToken: string;
|
||||||
|
accessTokenSecret: string;
|
||||||
|
bearerToken: string;
|
||||||
|
goLiveMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationsConfig {
|
||||||
|
browser: BrowserNotification;
|
||||||
|
discord: DiscordNotification;
|
||||||
|
twitter: TwitterNotification;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigDetails {
|
export interface ConfigDetails {
|
||||||
externalActions: ExternalAction[];
|
externalActions: ExternalAction[];
|
||||||
ffmpegPath: string;
|
ffmpegPath: string;
|
||||||
@@ -114,7 +141,8 @@ export interface ConfigDetails {
|
|||||||
forbiddenUsernames: string[];
|
forbiddenUsernames: string[];
|
||||||
suggestedUsernames: string[];
|
suggestedUsernames: string[];
|
||||||
chatDisabled: boolean;
|
chatDisabled: boolean;
|
||||||
|
federation: Federation;
|
||||||
|
notifications: NotificationsConfig;
|
||||||
chatJoinMessagesEnabled: boolean;
|
chatJoinMessagesEnabled: boolean;
|
||||||
chatEstablishedUserMode: boolean;
|
chatEstablishedUserMode: boolean;
|
||||||
federation: Federation;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -489,3 +489,78 @@ export const S3_TEXT_FIELDS_INFO = {
|
|||||||
tip: "If your S3 provider doesn't support virtual-hosted-style URLs set this to ON (i.e. Oracle Cloud Object Storage)",
|
tip: "If your S3 provider doesn't support virtual-hosted-style URLs set this to ON (i.e. Oracle Cloud Object Storage)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DISCORD_CONFIG_FIELDS = {
|
||||||
|
webhookUrl: {
|
||||||
|
fieldName: 'webhook',
|
||||||
|
label: 'Webhook URL',
|
||||||
|
maxLength: 255,
|
||||||
|
placeholder: 'https://discord.com/api/webhooks/837/jf38-6iNEv',
|
||||||
|
tip: 'The webhook assigned to your channel.',
|
||||||
|
type: TEXTFIELD_TYPE_URL,
|
||||||
|
pattern: DEFAULT_TEXTFIELD_URL_PATTERN,
|
||||||
|
useTrim: true,
|
||||||
|
},
|
||||||
|
goLiveMessage: {
|
||||||
|
fieldName: 'goLiveMessage',
|
||||||
|
label: 'Go Live Text',
|
||||||
|
maxLength: 300,
|
||||||
|
tip: 'The text to send when you go live.',
|
||||||
|
placeholder: `I've gone live! Come watch!`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BROWSER_PUSH_CONFIG_FIELDS = {
|
||||||
|
goLiveMessage: {
|
||||||
|
fieldName: 'goLiveMessage',
|
||||||
|
label: 'Go Live Text',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: 'The text to send when you go live.',
|
||||||
|
placeholder: `I've gone live! Come watch!`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TWITTER_CONFIG_FIELDS = {
|
||||||
|
apiKey: {
|
||||||
|
fieldName: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: '',
|
||||||
|
placeholder: `gaUQhRC2lqfrEFfElBXJgOctU`,
|
||||||
|
},
|
||||||
|
apiSecret: {
|
||||||
|
fieldName: 'apiSecret',
|
||||||
|
label: 'API Secret',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: '',
|
||||||
|
placeholder: `IIz4jFZMWbUKdFOEGUprFjRwIslG56d1SPQlolJYjXwJ2y2qKS`,
|
||||||
|
},
|
||||||
|
accessToken: {
|
||||||
|
fieldName: 'accessToken',
|
||||||
|
label: 'Access Token',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: '',
|
||||||
|
placeholder: `952540400-EEiwe9fkuSvWjnNC82YFa9kgpqbyAP3J7FjE2dkka`,
|
||||||
|
},
|
||||||
|
accessTokenSecret: {
|
||||||
|
fieldName: 'accessTokenSecret',
|
||||||
|
label: 'Access Token Secret',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: '',
|
||||||
|
placeholder: `xO0AZWNGfZxpNsYPg3zNEKhAsPPGvNZFlzQArA2khI9Kg`,
|
||||||
|
},
|
||||||
|
bearerToken: {
|
||||||
|
fieldName: 'bearerToken',
|
||||||
|
label: 'Bearer Token',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: '',
|
||||||
|
placeholder: `AAAAAAAAAAAAAAFqpXwEAAnnepHkjA8XD5ftx5jUadYIRtPtaq7AAAAwpXPpDWKDcdhiWr0tVDjsgW%2B4awGOM9VQ%3XPoMFuWcHsE42TK`,
|
||||||
|
},
|
||||||
|
goLiveMessage: {
|
||||||
|
fieldName: 'goLiveMessage',
|
||||||
|
label: 'Go Live Text',
|
||||||
|
maxLength: 200,
|
||||||
|
tip: 'The text to send when you go live.',
|
||||||
|
placeholder: `I've gone live! Come watch!`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -54,6 +54,19 @@ export const initialServerConfigState: ConfigDetails = {
|
|||||||
showEngagement: true,
|
showEngagement: true,
|
||||||
blockedDomains: [],
|
blockedDomains: [],
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
browser: { enabled: false, goLiveMessage: '' },
|
||||||
|
discord: { enabled: false, webhook: '', goLiveMessage: '' },
|
||||||
|
twitter: {
|
||||||
|
enabled: false,
|
||||||
|
goLiveMessage: '',
|
||||||
|
apiKey: '',
|
||||||
|
apiSecret: '',
|
||||||
|
accessToken: '',
|
||||||
|
accessTokenSecret: '',
|
||||||
|
bearerToken: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
externalActions: [],
|
externalActions: [],
|
||||||
supportedCodecs: [],
|
supportedCodecs: [],
|
||||||
videoCodec: '',
|
videoCodec: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user