mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Merge pull request #72 from storytold/bflat-moderation-controls
most needed moderations pages
This commit is contained in:
3 files changed
+406
-333
No files matched your search
+138
-126
@@ -1,32 +1,32 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { ApiConfig } from '@storyteller/components';
|
||||
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
|
||||
import { formatDistance } from 'date-fns';
|
||||
import { BackLink } from '../../_common/BackLink';
|
||||
import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import { ApiConfig } from "@storyteller/components";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { formatDistance } from "date-fns";
|
||||
import { BackLink } from "../../_common/BackLink";
|
||||
import { FrontendUrlConfig } from "../../../../common/FrontendUrlConfig";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
interface IpBanListResponse {
|
||||
success: boolean,
|
||||
ip_address_bans: Array<IpBanListItem>,
|
||||
success: boolean;
|
||||
ip_address_bans: Array<IpBanListItem>;
|
||||
}
|
||||
|
||||
interface IpBanListItem {
|
||||
ip_address: string,
|
||||
maybe_target_user_token: string,
|
||||
maybe_target_username: string,
|
||||
ip_address: string;
|
||||
maybe_target_user_token: string;
|
||||
maybe_target_username: string;
|
||||
|
||||
mod_user_token: string,
|
||||
mod_username: string,
|
||||
mod_display_name: string,
|
||||
mod_notes: string,
|
||||
mod_user_token: string;
|
||||
mod_username: string;
|
||||
mod_display_name: string;
|
||||
mod_notes: string;
|
||||
|
||||
created_at: string,
|
||||
updated_at: string,
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
function ModerationIpBanListFc(props: Props) {
|
||||
@@ -43,35 +43,35 @@ function ModerationIpBanListFc(props: Props) {
|
||||
const endpointUrl = api.getModerationIpBanList();
|
||||
|
||||
fetch(endpointUrl, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
credentials: "include",
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
const response : IpBanListResponse = res;
|
||||
if (!response.success) {
|
||||
return;
|
||||
}
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const response: IpBanListResponse = res;
|
||||
if (!response.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIpBanList(response.ip_address_bans)
|
||||
})
|
||||
.catch(e => {
|
||||
//this.props.onSpeakErrorCallback();
|
||||
});
|
||||
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<HTMLInputElement>) => {
|
||||
setNewIpAddress((ev.target as HTMLInputElement).value)
|
||||
setNewIpAddress((ev.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const handleModNotesChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
setModNotes((ev.target as HTMLInputElement).value)
|
||||
setModNotes((ev.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
|
||||
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>): boolean => {
|
||||
ev.preventDefault();
|
||||
|
||||
const api = new ApiConfig();
|
||||
@@ -80,41 +80,43 @@ function ModerationIpBanListFc(props: Props) {
|
||||
const request = {
|
||||
ip_address: newIpAddress,
|
||||
mod_notes: modNotes,
|
||||
}
|
||||
};
|
||||
|
||||
fetch(endpointUrl, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
credentials: "include",
|
||||
body: JSON.stringify(request),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
history.go(0); // NB: Force reload
|
||||
}
|
||||
})
|
||||
.catch(e => {});
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
history.go(0); // NB: Force reload
|
||||
}
|
||||
})
|
||||
.catch((e) => {});
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!props.sessionWrapper.canBanUsers()) {
|
||||
return <h1>Unauthorized</h1>;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
let rows : Array<JSX.Element> = [];
|
||||
let rows: Array<JSX.Element> = [];
|
||||
|
||||
ipBanList.forEach(ban => {
|
||||
ipBanList.forEach((ban) => {
|
||||
const modUserLink = `/profile/${ban.mod_username}`;
|
||||
const viewBanLink = `/moderation/ip_bans/${ban.ip_address}`;
|
||||
|
||||
const createTime = new Date(ban.created_at);
|
||||
const relativeCreateTime = formatDistance(createTime, now, { addSuffix: true });
|
||||
const relativeCreateTime = formatDistance(createTime, now, {
|
||||
addSuffix: true,
|
||||
});
|
||||
|
||||
rows.push(
|
||||
<tr key={ban.ip_address}>
|
||||
@@ -125,94 +127,104 @@ function ModerationIpBanListFc(props: Props) {
|
||||
<td>{ban.mod_notes}</td>
|
||||
<td>{relativeCreateTime}</td>
|
||||
<td>
|
||||
<Link to={viewBanLink}>view / edit</Link>
|
||||
<Link to={viewBanLink} className="btn btn-primary">
|
||||
View/Edit
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title is-1"> Moderation Ip Ban List </h1>
|
||||
|
||||
<p>
|
||||
<BackLink link={FrontendUrlConfig.moderationMain()} text="Back to moderation" />
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<p>IP Address bans will prevent bad actors from using and abusing the website.</p>
|
||||
|
||||
<br />
|
||||
|
||||
<h3 className="title is-3"> Create Ban </h3>
|
||||
<div className="container py-5">
|
||||
<h1 className="display-5 fw-bold">Moderation IP Ban List</h1>
|
||||
<p>
|
||||
IP Address bans will prevent bad actors from using and abusing the
|
||||
website.
|
||||
</p>
|
||||
<div className="pt-4">
|
||||
<BackLink
|
||||
link={FrontendUrlConfig.moderationMain()}
|
||||
text="Back to moderation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<div className="field">
|
||||
<label className="label">Ip Address</label>
|
||||
<div className="control has-icons-left has-icons-right">
|
||||
<input
|
||||
onChange={handleNewIpAddressChange}
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="IP Address, eg. 255.255.255.255"
|
||||
value={newIpAddress}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-envelope"></i>
|
||||
</span>
|
||||
<span className="icon is-small is-right">
|
||||
<i className="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
<div className="container-panel pt-3 pb-4">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Create Ban</h2>
|
||||
<div className="py-6">
|
||||
<div className="d-flex flex-column gap-4">
|
||||
<div>
|
||||
<label className="sub-title">IP Address</label>
|
||||
<div className="form-group">
|
||||
<input
|
||||
onChange={handleNewIpAddressChange}
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="IP Address, eg. 255.255.255.255"
|
||||
value={newIpAddress}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-envelope"></i>
|
||||
</span>
|
||||
<span className="icon is-small is-right">
|
||||
<i className="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
</div>
|
||||
{/*<p className="help">{invalidReason}</p>*/}
|
||||
</div>
|
||||
<div>
|
||||
<label className="sub-title">Moderator Notes</label>
|
||||
<div className="form-group">
|
||||
<input
|
||||
onChange={handleModNotesChange}
|
||||
className="form-control"
|
||||
type="text"
|
||||
placeholder="Notes / reason for ban"
|
||||
value={modNotes}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-envelope"></i>
|
||||
</span>
|
||||
<span className="icon is-small is-right">
|
||||
<i className="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
</div>
|
||||
{/*<p className="help">{invalidReason}</p>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*<p className="help">{invalidReason}</p>*/}
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Moderator Notes</label>
|
||||
<div className="control has-icons-left has-icons-right">
|
||||
<input
|
||||
onChange={handleModNotesChange}
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="Notes / reason for ban"
|
||||
value={modNotes}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-envelope"></i>
|
||||
</span>
|
||||
<span className="icon is-small is-right">
|
||||
<i className="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
</div>
|
||||
{/*<p className="help">{invalidReason}</p>*/}
|
||||
<div className="container pt-3 pb-5">
|
||||
<button className="btn btn-primary w-100">Create Ban</button>
|
||||
</div>
|
||||
|
||||
<button className="button is-danger is-large is-fullwidth">Create Ban</button>
|
||||
|
||||
</form>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<h3 className="title is-3"> Existing Bans </h3>
|
||||
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Moderator</th>
|
||||
<th>Moderator Notes</th>
|
||||
<th>Created At</th>
|
||||
<th>View / Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="container-panel py-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Existing Bans</h2>
|
||||
<div className="py-6 table-responsive">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Moderator</th>
|
||||
<th>Moderator Notes</th>
|
||||
<th>Created At</th>
|
||||
<th>View / Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { ModerationIpBanListFc };
|
||||
+185
-154
@@ -1,25 +1,39 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
|
||||
import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig';
|
||||
import { BackLink } from '../../_common/BackLink';
|
||||
import { GetTtsInferenceStats, GetTtsInferenceStatsIsOk } from '../../../api/moderation/GetTtsInferenceStats';
|
||||
import { GetW2lInferenceStats, GetW2lInferenceStatsIsOk } from '../../../api/moderation/GetW2lInferenceStats';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faBomb, faRedo } from '@fortawesome/free-solid-svg-icons';
|
||||
import { KillAction, KillTtsInferenceJobsIsSuccess, KillTtsInferenceJobs } from '@storyteller/components/src/api/moderation/tts/KillTtsInferenceJobs';
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { FrontendUrlConfig } from "../../../../common/FrontendUrlConfig";
|
||||
import { BackLink } from "../../_common/BackLink";
|
||||
import {
|
||||
GetTtsInferenceStats,
|
||||
GetTtsInferenceStatsIsOk,
|
||||
} from "../../../api/moderation/GetTtsInferenceStats";
|
||||
import {
|
||||
GetW2lInferenceStats,
|
||||
GetW2lInferenceStatsIsOk,
|
||||
} from "../../../api/moderation/GetW2lInferenceStats";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBomb, faRedo } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
KillAction,
|
||||
KillTtsInferenceJobsIsSuccess,
|
||||
KillTtsInferenceJobs,
|
||||
} from "@storyteller/components/src/api/moderation/tts/KillTtsInferenceJobs";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
function ModerationJobStatsFc(props: Props) {
|
||||
// NB: We have more TTS stats than W2L stats now.
|
||||
const [ttsSecondsSinceFirst, setTtsSecondsSinceFirst] = useState<number>(-1);
|
||||
const [ttsPendingJobCount, setTtsPendingJobCount] = useState<number>(-1);
|
||||
const [ttsPendingPriorityNonzeroJobCount, setTtsPendingPriorityNonzeroJobCount] = useState<number>(-1);
|
||||
const [ttsPendingPriorityGtOneJobCount, setTtsPendingPriorityGtOneJobCount] = useState<number>(-1);
|
||||
const [ttsAttemptFailedJobCount, setTtsAttemptFailedJobCount] = useState<number>(-1);
|
||||
|
||||
const [
|
||||
ttsPendingPriorityNonzeroJobCount,
|
||||
setTtsPendingPriorityNonzeroJobCount,
|
||||
] = useState<number>(-1);
|
||||
const [ttsPendingPriorityGtOneJobCount, setTtsPendingPriorityGtOneJobCount] =
|
||||
useState<number>(-1);
|
||||
const [ttsAttemptFailedJobCount, setTtsAttemptFailedJobCount] =
|
||||
useState<number>(-1);
|
||||
|
||||
const [w2lPendingJobCount, setW2lPendingJobCount] = useState<number>(-1);
|
||||
const [w2lSecondsSinceFirst, setW2lSecondsSinceFirst] = useState<number>(-1);
|
||||
@@ -29,12 +43,16 @@ function ModerationJobStatsFc(props: Props) {
|
||||
if (GetTtsInferenceStatsIsOk(response)) {
|
||||
setTtsSecondsSinceFirst(response.seconds_since_first);
|
||||
setTtsPendingJobCount(response.pending_count);
|
||||
setTtsPendingPriorityNonzeroJobCount(response.pending_priority_nonzero_count);
|
||||
setTtsPendingPriorityGtOneJobCount(response.pending_priority_gt_one_count);
|
||||
setTtsPendingPriorityNonzeroJobCount(
|
||||
response.pending_priority_nonzero_count
|
||||
);
|
||||
setTtsPendingPriorityGtOneJobCount(
|
||||
response.pending_priority_gt_one_count
|
||||
);
|
||||
setTtsAttemptFailedJobCount(response.attempt_failed_count);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const getW2lStats = useCallback(async () => {
|
||||
const response = await GetW2lInferenceStats();
|
||||
if (GetW2lInferenceStatsIsOk(response)) {
|
||||
@@ -54,26 +72,26 @@ function ModerationJobStatsFc(props: Props) {
|
||||
|
||||
const killPending = async () => {
|
||||
await doKill(KillAction.AllPending);
|
||||
}
|
||||
};
|
||||
|
||||
const killPendingAndFailed = async () => {
|
||||
await doKill(KillAction.AllPendingAndFailed);
|
||||
}
|
||||
};
|
||||
|
||||
const killZeroPriorityPending = async () => {
|
||||
await doKill(KillAction.ZeroPriorityPending);
|
||||
}
|
||||
};
|
||||
|
||||
const doKill = async(killAction: KillAction) => {
|
||||
const doKill = async (killAction: KillAction) => {
|
||||
const response = await KillTtsInferenceJobs(killAction);
|
||||
|
||||
if (KillTtsInferenceJobsIsSuccess(response)) {
|
||||
reloadStats();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (ttsPendingJobCount === -1 && w2lPendingJobCount === -1) {
|
||||
return <div />
|
||||
return <div />;
|
||||
}
|
||||
|
||||
if (!props.sessionWrapper.canBanUsers()) {
|
||||
@@ -85,148 +103,161 @@ function ModerationJobStatsFc(props: Props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title is-1"> Job Stats </h1>
|
||||
<div className="container py-5">
|
||||
<h1 className="display-5 fw-bold">Job Stats</h1>
|
||||
<div className="pt-3">
|
||||
<BackLink
|
||||
link={FrontendUrlConfig.moderationMain()}
|
||||
text="Back to moderation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BackLink link={FrontendUrlConfig.moderationMain()} text="Back to moderation" />
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">TTS Stats</h2>
|
||||
<div className="py-6">
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info">
|
||||
<u>Misc Stats</u>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>TTS wait time</th>
|
||||
<td>{ttsWait}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info">
|
||||
<u>Failed Jobs by Type</u>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Pending Jobs (All)</th>
|
||||
<td>{ttsPendingJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Pending Jobs (Priority > 0)</th>
|
||||
<td>{ttsPendingPriorityNonzeroJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Pending Jobs (Priority > 1)</th>
|
||||
<td>{ttsPendingPriorityGtOneJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Failed Jobs w/ Retry</th>
|
||||
<td>{ttsAttemptFailedJobCount} pending </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<h2 className="title is-3">TTS Stats</h2>
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">W2L Stats</h2>
|
||||
<div className="py-6">
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info">
|
||||
<u>Misc Stats</u>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>W2L wait time</th>
|
||||
<td>{w2lWait}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info">
|
||||
<u>Failed Jobs by Type</u>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Pending Jobs (All)</th>
|
||||
<td>{w2lPendingJobCount} pending </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info"><u>Misc Stats</u></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
TTS wait time
|
||||
</th>
|
||||
<td>{ttsWait}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Reload Job Stats</h2>
|
||||
<div className="py-6">
|
||||
<button
|
||||
className="btn btn-primary w-100"
|
||||
onClick={() => reloadStats()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRedo} className="me-2" />
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info"><u>Failed Jobs by Type</u></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Pending Jobs (All)
|
||||
</th>
|
||||
<td>{ttsPendingJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Pending Jobs (Priority > 0)
|
||||
</th>
|
||||
<td>{ttsPendingPriorityNonzeroJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Pending Jobs (Priority > 1)
|
||||
</th>
|
||||
<td>{ttsPendingPriorityGtOneJobCount} pending </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Failed Jobs w/ Retry
|
||||
</th>
|
||||
<td>{ttsAttemptFailedJobCount} pending </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Kill Stuck Jobs (Danger Zone)</h2>
|
||||
<div className="py-6">
|
||||
<h5 className="fw-semibold mb-4">(Please don't do this.)</h5>
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<button
|
||||
className="btn btn-destructive w-100"
|
||||
onClick={() => killZeroPriorityPending()}
|
||||
>
|
||||
Kill Zero-Priority Pending TTS
|
||||
</button>
|
||||
|
||||
<br />
|
||||
<button
|
||||
className="btn btn-destructive w-100"
|
||||
onClick={() => killPending()}
|
||||
>
|
||||
Kill ALL Pending TTS
|
||||
<FontAwesomeIcon icon={faBomb} />
|
||||
</button>
|
||||
|
||||
<h2 className="title is-3">W2L Stats</h2>
|
||||
<button
|
||||
className="btn btn-destructive w-100"
|
||||
onClick={() => killPendingAndFailed()}
|
||||
>
|
||||
Kill ALL Pending and Failed TTS
|
||||
<FontAwesomeIcon icon={faBomb} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info"><u>Misc Stats</u></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
W2L wait time
|
||||
</th>
|
||||
<td>{w2lWait}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<table className="table is-fullwidth is-bordered is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2} className="is-info"><u>Failed Jobs by Type</u></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Pending Jobs (All)
|
||||
</th>
|
||||
<td>{w2lPendingJobCount} pending </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<h2 className="title is-4">Reload Job Stats</h2>
|
||||
|
||||
<button
|
||||
className="button is-large is-primary is-fullwidth"
|
||||
onClick={() => reloadStats()}
|
||||
>Reload <FontAwesomeIcon icon={faRedo} /></button>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<h2 className="title is-4">Kill Stuck Jobs (Danger Zone)</h2>
|
||||
<h2 className="subtitle is-6">(Please don't do this.)</h2>
|
||||
|
||||
<button
|
||||
className="button is-large is-warning is-fullwidth"
|
||||
onClick={() => killZeroPriorityPending()}
|
||||
>Kill Zero-Priority Pending TTS</button>
|
||||
|
||||
<br />
|
||||
|
||||
<button
|
||||
className="button is-large is-danger is-fullwidth"
|
||||
onClick={() => killPending()}
|
||||
>Kill ALL Pending TTS <FontAwesomeIcon icon={faBomb} /></button>
|
||||
|
||||
<br />
|
||||
|
||||
<button
|
||||
className="button is-large is-danger is-fullwidth"
|
||||
onClick={() => killPendingAndFailed()}
|
||||
>Kill ALL Pending and Failed TTS <FontAwesomeIcon icon={faBomb} /></button>
|
||||
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<BackLink link={FrontendUrlConfig.moderationMain()} text="Back to moderation" />
|
||||
<div className="container pb-5">
|
||||
<BackLink
|
||||
link={FrontendUrlConfig.moderationMain()}
|
||||
text="Back to moderation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function humanWaitTime(seconds: number) : string {
|
||||
function humanWaitTime(seconds: number): string {
|
||||
if (seconds === -1) {
|
||||
return 'error';
|
||||
return "error";
|
||||
} else if (seconds < 60) {
|
||||
return `${seconds} seconds`;
|
||||
} else if (seconds < 60 * 60) {
|
||||
|
||||
@@ -1,74 +1,104 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig';
|
||||
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FrontendUrlConfig } from "../../../../common/FrontendUrlConfig";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faBan,
|
||||
faClipboardCheck,
|
||||
faListCheck,
|
||||
faMicrophone,
|
||||
faTags,
|
||||
faUsers,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper,
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
function ModerationFc(props: Props) {
|
||||
|
||||
if (!props.sessionWrapper.canBanUsers()) {
|
||||
return <h1>Unauthorized</h1>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title is-1"> Moderation Controls </h1>
|
||||
<div className="container py-5">
|
||||
<h1 className="display-5 fw-bold text-center text-lg-start">
|
||||
Moderation Controls
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<h3 className="title is-4"> Emergency </h3>
|
||||
<Link
|
||||
to="/moderation/ip_bans"
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>IP Bans</Link>
|
||||
|
||||
<br />
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Emergency</h2>
|
||||
<div className="py-6">
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<Link to="/moderation/ip_bans" className="btn btn-primary w-100">
|
||||
<FontAwesomeIcon icon={faBan} className="me-2" />
|
||||
IP Bans
|
||||
</Link>
|
||||
<Link
|
||||
to="/moderation/job_stats"
|
||||
className="btn btn-primary w-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faListCheck} className="me-2" />
|
||||
Job Stats
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
to="/moderation/job_stats"
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>Job Stats</Link>
|
||||
|
||||
<br />
|
||||
<div className="container-panel pt-3 pb-5">
|
||||
<div className="panel p-3 p-lg-4">
|
||||
<h2 className="panel-title fw-bold">Lookup, Stats, and Editing</h2>
|
||||
<div className="py-6">
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<Link
|
||||
to="/moderation/user/list"
|
||||
className="btn btn-secondary w-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faUsers} className="me-2" />
|
||||
User List
|
||||
</Link>
|
||||
|
||||
<h3 className="title is-4"> Lookup, Stats, and Editing </h3>
|
||||
<Link
|
||||
to={FrontendUrlConfig.moderationTtsCategoryList()}
|
||||
className="btn btn-secondary w-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTags} className="me-2" />
|
||||
Manage TTS Categories
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to="/moderation/user/list"
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>User List</Link>
|
||||
<Link
|
||||
to="/moderation/approve/w2l_templates"
|
||||
className="btn btn-secondary w-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faClipboardCheck} className="me-2" />
|
||||
Unapproved W2L Templates
|
||||
</Link>
|
||||
|
||||
<br />
|
||||
|
||||
<Link
|
||||
to={FrontendUrlConfig.moderationTtsCategoryList()}
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>Manage TTS Categories</Link>
|
||||
|
||||
<br />
|
||||
|
||||
<Link
|
||||
to="/moderation/approve/w2l_templates"
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>Unapproved W2L Templates</Link>
|
||||
|
||||
<br />
|
||||
|
||||
<Link
|
||||
to="/moderation/voice_stats"
|
||||
className="button is-link is-large is-fullwidth is-light"
|
||||
>Voice Stats</Link>
|
||||
|
||||
<br />
|
||||
|
||||
<p>
|
||||
More mod controls will be added in the future: user roles, activity tracking,
|
||||
timed bans, account bans, etc.
|
||||
</p>
|
||||
<Link
|
||||
to="/moderation/voice_stats"
|
||||
className="btn btn-secondary w-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faMicrophone} className="me-2" />
|
||||
Voice Stats
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container pb-5">
|
||||
<p>
|
||||
More mod controls will be added in the future: user roles, activity
|
||||
tracking, timed bans, account bans, etc.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { ModerationFc };
|
||||
Reference in new issue
Block a user