reorganize server dashboard into categories

This commit is contained in:
janderedev 2022-03-16 20:34:24 +01:00
parent 180f722ed3
commit b077943e41
No known key found for this signature in database
GPG key ID: 5D5E18ACB990F57A
4 changed files with 233 additions and 123 deletions

View file

@ -5,7 +5,7 @@ import '@revoltchat/ui/src/styles/dark.css';
import '@revoltchat/ui/src/styles/common.css'; import '@revoltchat/ui/src/styles/common.css';
import RequireAuth from './components/RequireAuth'; import RequireAuth from './components/RequireAuth';
import DashboardHome from './pages/DashboardHome'; import DashboardHome from './pages/DashboardHome';
import ServerDashboard from './pages/ServerDashboard'; import ServerDashboard from './pages/ServerDashboard/ServerDashboard';
const API_URL = import.meta.env.VITE_API_URL?.toString() const API_URL = import.meta.env.VITE_API_URL?.toString()
|| 'http://localhost:9000'; || 'http://localhost:9000';

View file

@ -0,0 +1,20 @@
import { FunctionComponent, useState } from "react";
import './styles/CategorySelector.css';
const CategorySelector: FunctionComponent<{ keys: { id: string, name: string }[], selected: string, onChange: (key: string) => void }> = (props) => {
return (
<div className="category-selector-outer">
{props.keys.map((k) => (
<div
className={`category-selector-inner ${props.selected == k.id ? 'selected' : ''}`}
key={k.id}
onClick={() => props.onChange(k.id)}
>
<span>{k.name}</span>
</div>
))}
</div>
);
}
export default CategorySelector;

View file

@ -0,0 +1,40 @@
.category-selector-outer {
width: calc(100% - 20px);
margin: 8px 10px;
height: 32px;
background-color: var(--secondary-background);
display: flex;
border-radius: 6px;
justify-content: space-evenly;
}
.category-selector-inner {
background-color: var(--tertiary-background);
height: 24px;
margin: 4px;
width: 100vw;
user-select: none;
text-align: center;
overflow: hidden;
border-radius: 4px;
transition: filter .2s, background-color .3s;
}
.category-selector-inner:hover {
filter: brightness(1.1);
}
.category-selector-inner:active {
filter: brightness(1.2);
}
.category-selector-inner.selected {
background-color: var(--accent);
}
.category-selector-inner span {
color: var(--secondary-foreground);
}
.category-selector-inner.selected span {
color: var(--foreground);
}

View file

@ -10,10 +10,11 @@ import { H3 } from '@revoltchat/ui/lib/components/atoms/heading/H3';
import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4'; import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4';
import { Icon } from '@mdi/react'; import { Icon } from '@mdi/react';
import { mdiChevronLeft, mdiCloseBox } from '@mdi/js'; import { mdiChevronLeft, mdiCloseBox } from '@mdi/js';
import { API_URL } from "../App"; import { API_URL } from "../../App";
import { getAuthHeaders } from "../utils"; import { getAuthHeaders } from "../../utils";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import defaultChannelIcon from '../assets/channel-default-icon.svg'; import defaultChannelIcon from '../../assets/channel-default-icon.svg';
import CategorySelector from '../../components/CategorySelector';
type User = { id: string, username?: string, avatarURL?: string } type User = { id: string, username?: string, avatarURL?: string }
type Channel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean } type Channel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean }
@ -40,6 +41,8 @@ type AntispamRule = {
} }
const ServerDashboard: FunctionComponent = () => { const ServerDashboard: FunctionComponent = () => {
const [category, setCategory] = useState('home');
const [serverInfo, setServerInfo] = useState({} as Server); const [serverInfo, setServerInfo] = useState({} as Server);
const [status, setStatus] = useState(''); const [status, setStatus] = useState('');
@ -77,7 +80,7 @@ const ServerDashboard: FunctionComponent = () => {
try { try {
const res = await axios.get(`${API_URL}/dash/server/${serverid}`, { headers: await getAuthHeaders() }); const res = await axios.get(`${API_URL}/dash/server/${serverid}`, { headers: await getAuthHeaders() });
console.log(res.data); console.log(res.data);
const server: Server = res.data.server; const server: Server = res.data.server;
setServerInfo(server); setServerInfo(server);
@ -108,131 +111,179 @@ const ServerDashboard: FunctionComponent = () => {
return ( return (
<> <>
<Link to='/dashboard'> {status.length ? <a>{status}</a> : <></>}
<div style={{ display: 'flex', marginTop: '4px' }}> <div
<Icon path={mdiChevronLeft} style={{ height: '24px' }} /> style={{
<span>Back</span> marginTop: '8px',
</div> marginLeft: '8px',
</Link> textOverflow: 'ellipsis',
<H1 style={{ marginTop: '8px' }}>{serverInfo?.name ?? 'Loading...'}</H1> whiteSpace: 'nowrap',
{status.length ? <a>{status}</a> : <br/>} overflow: 'hidden',
color: 'var(--secondary-foreground)',
maxWidth: 'calc(100% - 20px)',
}}
>
<Link to='/dashboard' style={{ float: 'left' }}>
<div style={{ display: 'flex' }}>
<Icon path={mdiChevronLeft} style={{ height: '25px' }} />
<span>Back</span>
</div>
</Link>
<span
style={{
color: 'var(--foreground)',
marginLeft: '8px',
}}
>
{serverInfo?.name ?? 'Loading...'}
</span>
<span style={{ color: 'var(--secondary-foreground)', marginLeft: '6px' }}>
</span>
<span
style={{
color: 'var(--secondary-foreground)',
marginLeft: '6px',
}}
>
{serverInfo.description || <i>No server description set</i>}
</span>
</div>
<CategorySelector
keys={[
{ id: 'home', name: 'Home' },
{ id: 'automod', name: 'Moderation Rules' },
]}
selected={category}
onChange={setCategory}
/>
<div hidden={Object.keys(serverInfo).length == 0}> <div hidden={Object.keys(serverInfo).length == 0}>
<H4>{serverInfo.description ?? <i>No server description set</i>}</H4>
<br/>
<div style={{ paddingLeft: '10px', paddingRight: '10px' }}> <div style={{ paddingLeft: '10px', paddingRight: '10px' }}>
<>
<H3>Prefix</H3>
<InputBox
style={{ width: '150px', }}
placeholder="Enter a prefix..."
value={prefix}
onChange={e => {
setPrefix(e.currentTarget.value);
setChanged({ ...changed, prefix: true });
}}
/>
<Checkbox
style={{ maxWidth: '400px' }}
value={prefixAllowSpace}
onChange={() => {
setPrefixAllowSpace(!prefixAllowSpace);
setChanged({ ...changed, prefixAllowSpace: true });
}}
title="Allow space after prefix"
description={'Whether the bot recognizes a command if the prefix is followed by a space. Enable if your prefix is a word.'}
/>
<Button
style={{ marginTop: "16px" }}
onClick={saveConfig}
>Save</Button>
</>
<LineDivider /> {category == 'home' && (
<>
<>
<H3>Prefix</H3>
<InputBox
style={{ width: '150px', }}
placeholder="Enter a prefix..."
value={prefix}
onChange={e => {
setPrefix(e.currentTarget.value);
setChanged({ ...changed, prefix: true });
}}
/>
<Checkbox
style={{ maxWidth: '400px' }}
value={prefixAllowSpace}
onChange={() => {
setPrefixAllowSpace(!prefixAllowSpace);
setChanged({ ...changed, prefixAllowSpace: true });
}}
title="Allow space after prefix"
description={'Whether the bot recognizes a command if the prefix is followed by a space. Enable if your prefix is a word.'}
/>
<Button
style={{ marginTop: "16px" }}
onClick={saveConfig}
>Save</Button>
</>
<> <LineDivider />
<H3>Bot Managers</H3>
<H4>
Only users with "Manage Server" permission are allowed to add/remove other
bot managers and are automatically considered bot manager.
</H4>
<UserListTypeContainer>
<UserListContainer disabled={(serverInfo.perms ?? 0) < 3}>
{botManagers.map((uid: string) => {
const user = serverInfo.users.find(u => u.id == uid) || { id: uid }
return (
<UserListEntry type='MANAGER' user={user} key={uid} />
)})}
<UserListAddField type='MANAGER' />
</UserListContainer>
</UserListTypeContainer>
<H3>Moderators</H3> <>
<H4> <H3>Bot Managers</H3>
Only bot managers are allowed to add/remove moderators. <H4>
All bot managers are also moderators. Only users with "Manage Server" permission are allowed to add/remove other
</H4> bot managers and are automatically considered bot manager.
<UserListTypeContainer> </H4>
<UserListContainer disabled={(serverInfo.perms ?? 0) < 2}> <UserListTypeContainer>
{moderators.map((uid: string) => { <UserListContainer disabled={(serverInfo.perms ?? 0) < 3}>
const user = serverInfo.users.find(u => u.id == uid) || { id: uid } {botManagers.map((uid: string) => {
return ( const user = serverInfo.users.find(u => u.id == uid) || { id: uid }
<UserListEntry type='MOD' user={user} key={uid} /> return (
)})} <UserListEntry type='MANAGER' user={user} key={uid} />
<UserListAddField type='MOD' /> )})}
</UserListContainer> <UserListAddField type='MANAGER' />
</UserListTypeContainer> </UserListContainer>
</> </UserListTypeContainer>
<LineDivider /> <H3>Moderators</H3>
<H4>
Only bot managers are allowed to add/remove moderators.
All bot managers are also moderators.
</H4>
<UserListTypeContainer>
<UserListContainer disabled={(serverInfo.perms ?? 0) < 2}>
{moderators.map((uid: string) => {
const user = serverInfo.users.find(u => u.id == uid) || { id: uid }
return (
<UserListEntry type='MOD' user={user} key={uid} />
)})}
<UserListAddField type='MOD' />
</UserListContainer>
</UserListTypeContainer>
</>
</>
)}
<> {category == 'automod' && (
<H3>Antispam Rules</H3> <>
{serverInfo.perms != null && automodSettings && ( <H3>Antispam Rules</H3>
serverInfo.perms > 0 {serverInfo.perms != null && automodSettings && (
? ( serverInfo.perms > 0
<> ? (
{automodSettings.antispam.map(r => <AntispamRule rule={r} key={r.id} />)} <>
<Button style={{ {automodSettings.antispam.map((r, i) => (
marginTop: '12px', <>
marginBottom: '8px', <AntispamRule rule={r} key={r.id} />
}} onClick={async () => { {i < automodSettings.antispam.length - 1 && <LineDivider/>}
const newRule: AntispamRule = { </>
action: 0, ))}
max_msg: 5, <Button style={{
timeframe: 3, marginTop: '12px',
message: null, marginBottom: '8px',
id: '', }} onClick={async () => {
channels: [], const newRule: AntispamRule = {
} action: 0,
max_msg: 5,
timeframe: 3,
message: null,
id: '',
channels: [],
}
const res = await axios.post( const res = await axios.post(
`${API_URL}/dash/server/${serverid}/automod`, `${API_URL}/dash/server/${serverid}/automod`,
{ {
action: newRule.action, action: newRule.action,
max_msg: newRule.max_msg, max_msg: newRule.max_msg,
timeframe: newRule.timeframe, timeframe: newRule.timeframe,
}, },
{ headers: await getAuthHeaders() } { headers: await getAuthHeaders() }
); );
newRule.id = res.data.id; newRule.id = res.data.id;
setAutomodSettings({ antispam: [ ...(automodSettings.antispam), newRule ] }); setAutomodSettings({ antispam: [ ...(automodSettings.antispam), newRule ] });
}}> }}>
Create Rule Create Rule
</Button> </Button>
</> </>
)
: (
<div>
<p style={{ color: 'var(--foreground)' }}>
You do not have access to this.
</p>
</div>
)
) )
: ( }
<div> </>
<p style={{ color: 'var(--foreground)' }}> )}
You do not have access to this.
</p>
</div>
)
)
}
</>
</div> </div>
</div> </div>
</> </>
@ -296,7 +347,7 @@ const ServerDashboard: FunctionComponent = () => {
`${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}/${props.user.id}`, `${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}/${props.user.id}`,
{ headers: await getAuthHeaders() } { headers: await getAuthHeaders() }
); );
if (props.type == 'MANAGER') { if (props.type == 'MANAGER') {
setBotManagers(res.data.managers); setBotManagers(res.data.managers);
} }
@ -308,7 +359,7 @@ const ServerDashboard: FunctionComponent = () => {
</div> </div>
); );
} }
function UserListContainer(props: { disabled: boolean, children: any }) { function UserListContainer(props: { disabled: boolean, children: any }) {
return ( return (
<div <div
@ -406,7 +457,7 @@ const ServerDashboard: FunctionComponent = () => {
.find(c => c.name == content) .find(c => c.name == content)
|| serverInfo.channels // Prefer channel with same capitalization, || serverInfo.channels // Prefer channel with same capitalization,
.find(c => c.name.toLowerCase() == content.toLowerCase()); // otherwise search case insensitive .find(c => c.name.toLowerCase() == content.toLowerCase()); // otherwise search case insensitive
if (channel && channel.type == 'TEXT') { if (channel && channel.type == 'TEXT') {
props.onInput(channel); props.onInput(channel);
setContent(''); setContent('');
@ -613,7 +664,6 @@ const ServerDashboard: FunctionComponent = () => {
{props.rule.id} {props.rule.id}
</code> </code>
<div style={{ clear: 'both' }} /> <div style={{ clear: 'both' }} />
<div style={{ maxWidth: 'max(40%, 600px)' }}><LineDivider/></div>
</div> </div>
</div> </div>
) )