diff --git a/vocodes/src/common/ApiConfig.ts b/vocodes/src/common/ApiConfig.ts index 4680a53fc8..83f9c8081f 100644 --- a/vocodes/src/common/ApiConfig.ts +++ b/vocodes/src/common/ApiConfig.ts @@ -198,6 +198,18 @@ class ApiConfig { return `${this.getScheme()}://${this.getNewApiHost()}/events`; } + getModerationIpBanList(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/moderation/ip_bans/list`; + } + + createModerationIpBan(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/moderation/ip_bans/add`; + } + + deleteModerationIpBan(ipAddress: string): string { + return `${this.getScheme()}://${this.getNewApiHost()}/moderation/ip_bans/${ipAddress}/delete`; + } + private getScheme() : string { return this.isLocalDev ? "http" : "https"; } diff --git a/vocodes/src/v2/NewFooterNavFc.tsx b/vocodes/src/v2/NewFooterNavFc.tsx index fe8c4495f4..162b1e1871 100644 --- a/vocodes/src/v2/NewFooterNavFc.tsx +++ b/vocodes/src/v2/NewFooterNavFc.tsx @@ -7,9 +7,19 @@ interface Props { } function NewFooterNavFc(props: Props) { + + let moderationLink = ; + + if (props.sessionWrapper.canBanUsers()) { + moderationLink = ( + Mod Controls + ); + } + return (

+ {moderationLink}

We're trying to build a democratized future of creativity, filmmaking and storytelling. Contact @echelon for more information. diff --git a/vocodes/src/v2/NewVocodesContainer.tsx b/vocodes/src/v2/NewVocodesContainer.tsx index a6ef9f6fbc..ce326218fe 100644 --- a/vocodes/src/v2/NewVocodesContainer.tsx +++ b/vocodes/src/v2/NewVocodesContainer.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { FirehoseEventListFc } from './firehose/FirehoseEventListFc'; import { LoginFc } from './login/LoginFc'; +import { ModerationFc } from './moderation/ModerationFc'; +import { ModerationIpBanListFc } from './moderation_ip_ban_list/ModerationIpBanListFc'; import { NewFooterNavFc } from './NewFooterNavFc'; import { NewTopNavFc } from './NewTopNavFc'; import { ProfileEditFc } from './profile_edit/ProfileEditFc'; @@ -15,7 +17,6 @@ import { TtsModelUploadJob } from '../jobs/TtsModelUploadJobs'; import { TtsModelViewFc } from './tts_model_view/TtsModelViewFc'; import { TtsResultViewFc } from './tts_result_view/TtsResultViewFc'; import { UploadChoiceFc } from './upload/UploadChoiceFc'; -import { UploadComponent } from './upload/UploadComponent'; import { UploadTtsModelFc } from './upload/UploadTtsModelFc'; import { UploadW2lPhotoFc } from './upload/UploadW2lPhotoFc'; import { UploadW2lVideoFc } from './upload/UploadW2lVideoFc'; @@ -176,6 +177,18 @@ class NewVocodesContainer extends React.Component { /> + + + + + + + + Unauthorized; + } + + return ( +

+

Moderation Controls

+ + IP Ban List + +
+

More mod controls will be added in the future: user roles, activity tracking, timed bans, account bans, etc.

+ + +
+ ) +} + +export { ModerationFc }; diff --git a/vocodes/src/v2/moderation_ip_ban_list/ModerationIpBanListFc.tsx b/vocodes/src/v2/moderation_ip_ban_list/ModerationIpBanListFc.tsx new file mode 100644 index 0000000000..ca2f7113a3 --- /dev/null +++ b/vocodes/src/v2/moderation_ip_ban_list/ModerationIpBanListFc.tsx @@ -0,0 +1,189 @@ +import React, { useEffect, useState } from 'react'; +import { ApiConfig } from '../../common/ApiConfig'; +import { SessionWrapper } from '../../session/SessionWrapper'; + +interface Props { + sessionWrapper: SessionWrapper, +} + +interface IpBanListResponse { + success: boolean, + ip_address_bans: Array, +} + +interface IpBanListItem { + ip_address: string, + maybe_target_user_token: string, + maybe_target_username: string, + + mod_user_token: string, + mod_notes: string, + + created_at: string, + updated_at: string, +} + +function ModerationIpBanListFc(props: Props) { + const [ipBanList, setIpBanList] = useState>([]); + + // Form + const [newIpAddress, setNewIpAddress] = useState(""); + const [modNotes, setModNotes] = useState(""); + + useEffect(() => { + const api = new ApiConfig(); + const endpointUrl = api.getModerationIpBanList(); + + fetch(endpointUrl, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include', + }) + .then(res => res.json()) + .then(res => { + const response : IpBanListResponse = res; + if (!response.success) { + return; + } + + setIpBanList(response.ip_address_bans) + }) + .catch(e => { + //this.props.onSpeakErrorCallback(); + }); + }, []); // NB: Empty array dependency sets to run ONLY on mount + + const handleNewIpAddressChange = (ev: React.FormEvent) => { + setNewIpAddress((ev.target as HTMLInputElement).value) + }; + + const handleModNotesChange = (ev: React.FormEvent) => { + setModNotes((ev.target as HTMLInputElement).value) + }; + + const handleFormSubmit = (ev: React.FormEvent) : boolean => { + ev.preventDefault(); + + const api = new ApiConfig(); + const endpointUrl = api.createModerationIpBan(); + + const request = { + ip_address: newIpAddress, + mod_notes: modNotes, + } + + fetch(endpointUrl, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(request), + }) + .then(res => res.json()) + .then(res => { + if (res.success) { + history.go(0); // NB: Force reload + } + }) + .catch(e => {}); + + return false; + } + + if (!props.sessionWrapper.canBanUsers()) { + return

Unauthorized

; + } + + let rows : Array = []; + + ipBanList.forEach(ban => { + + rows.push( + + {ban.ip_address} + {ban.mod_notes} + {ban.created_at} + + ) + }); + + return ( +
+

Moderation Ip Ban List

+ +

IP Address bans will prevent bad actors from using and abusing the website.

+
+ +

Create Ban

+ +
+
+ +
+ + + + + + + +
+ {/*

{invalidReason}

*/} +
+ +
+ +
+ + + + + + + +
+ {/*

{invalidReason}

*/} +
+ + + +
+ +
+
+ +

Existing Bans

+ + + + + + + + + + + {rows} + +
IP AddressModerator NotesCreated At
+
+ ) +} + +export { ModerationIpBanListFc };