mirror of
				https://github.com/owncast/owncast.git
				synced 2025-10-31 18:18:06 +08:00 
			
		
		
		
	![renovate[bot]](/assets/img/avatar_default.png) 44eafe69ac
			
		
	
	44eafe69ac
	
	
	
		
			
			* fix(deps): update dependency date-fns to v3 * fix(deps): update usage of date-fns format --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com>
		
			
				
	
	
		
			197 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Button, Col, Collapse, Row, Spin, Table, Tag } from 'antd';
 | |
| import { FC, useEffect, useState } from 'react';
 | |
| import { format } from 'date-fns';
 | |
| import { ColumnsType } from 'antd/lib/table';
 | |
| import dynamic from 'next/dynamic';
 | |
| import { ErrorBoundary } from 'react-error-boundary';
 | |
| import ChatModeration from '../../../services/moderation-service';
 | |
| import styles from './ChatModerationDetailsModal.module.scss';
 | |
| import { formatUAstring } from '../../../utils/format';
 | |
| import { ComponentError } from '../../ui/ComponentError/ComponentError';
 | |
| 
 | |
| const { Panel } = Collapse;
 | |
| 
 | |
| // Lazy loaded components
 | |
| 
 | |
| const DeleteOutlined = dynamic(() => import('@ant-design/icons/DeleteOutlined'), {
 | |
|   ssr: false,
 | |
| });
 | |
| 
 | |
| export type ChatModerationDetailsModalProps = {
 | |
|   userId: string;
 | |
|   accessToken: string;
 | |
| };
 | |
| 
 | |
| export interface UserDetails {
 | |
|   user: User;
 | |
|   connectedClients: Client[];
 | |
|   messages: Message[];
 | |
| }
 | |
| 
 | |
| export interface Client {
 | |
|   messageCount: number;
 | |
|   userAgent: string;
 | |
|   connectedAt: Date;
 | |
|   geo: string;
 | |
|   id: number;
 | |
| }
 | |
| 
 | |
| export interface Message {
 | |
|   id: string;
 | |
|   timestamp: Date;
 | |
|   user: null;
 | |
|   body: string;
 | |
| }
 | |
| 
 | |
| export interface User {
 | |
|   id: string;
 | |
|   displayName: string;
 | |
|   displayColor: number;
 | |
|   createdAt: Date;
 | |
|   previousNames: string[];
 | |
|   scopes: string[];
 | |
|   isBot: boolean;
 | |
|   authenticated: boolean;
 | |
| }
 | |
| 
 | |
| const removeMessage = async (messageId: string, accessToken: string) => {
 | |
|   try {
 | |
|     ChatModeration.removeMessage(messageId, accessToken);
 | |
|   } catch (e) {
 | |
|     console.error(e);
 | |
|   }
 | |
| };
 | |
| 
 | |
| const ValueRow = ({ label, value }: { label: string; value: string }) => (
 | |
|   <Row justify="space-around" align="middle">
 | |
|     <Col span={12}>{label}</Col>
 | |
|     <Col span={12}>{value}</Col>
 | |
|   </Row>
 | |
| );
 | |
| 
 | |
| const ConnectedClient = ({ client }: { client: Client }) => {
 | |
|   const { messageCount, connectedAt, geo } = client;
 | |
|   const connectedAtDate = format(new Date(connectedAt), 'PP pp');
 | |
| 
 | |
|   return (
 | |
|     <div>
 | |
|       <ValueRow label="Messages Sent" value={messageCount.toString()} />
 | |
|       {geo !== 'N/A' && <ValueRow label="Geo" value={geo} />}
 | |
|       <ValueRow label="Connected At" value={connectedAtDate} />
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| const UserColorBlock = ({ color }) => {
 | |
|   const bg = `var(--theme-color-users-${color})`;
 | |
|   return (
 | |
|     <div className={styles.colorBlock} style={{ backgroundColor: bg }}>
 | |
|       Color {color}
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export const ChatModerationDetailsModal: FC<ChatModerationDetailsModalProps> = ({
 | |
|   userId,
 | |
|   accessToken,
 | |
| }) => {
 | |
|   const [userDetails, setUserDetails] = useState<UserDetails | null>(null);
 | |
|   const [loading, setLoading] = useState(true);
 | |
| 
 | |
|   const getDetails = async () => {
 | |
|     try {
 | |
|       const response = await (
 | |
|         await fetch(`/api/moderation/chat/user/${userId}?accessToken=${accessToken}`)
 | |
|       ).json();
 | |
|       setUserDetails(response);
 | |
|       setLoading(false);
 | |
|     } catch (e) {
 | |
|       console.error(e);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   useEffect(() => {
 | |
|     getDetails();
 | |
|   }, []);
 | |
| 
 | |
|   if (!userDetails) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   const { user, connectedClients, messages } = userDetails;
 | |
|   const { displayColor, createdAt, previousNames, scopes, isBot, authenticated } = user;
 | |
| 
 | |
|   const totalMessagesSent = connectedClients.reduce((acc, client) => acc + client.messageCount, 0);
 | |
|   const createdAtDate = format(new Date(createdAt), 'PP pp');
 | |
| 
 | |
|   const chatMessageColumns: ColumnsType<Message> = [
 | |
|     {
 | |
|       title: 'Message',
 | |
|       dataIndex: 'body',
 | |
|       key: 'body',
 | |
|     },
 | |
|     {
 | |
|       title: 'Sent At',
 | |
|       dataIndex: 'timestamp',
 | |
|       key: 'timestamp',
 | |
|       render: timestamp => format(new Date(timestamp), 'PP pp'),
 | |
|     },
 | |
|     {
 | |
|       title: 'Delete',
 | |
|       key: 'delete',
 | |
|       render: (_text, record) => (
 | |
|         <Button
 | |
|           type="primary"
 | |
|           ghost
 | |
|           icon={<DeleteOutlined />}
 | |
|           onClick={() => removeMessage(record.id, accessToken)}
 | |
|         />
 | |
|       ),
 | |
|     },
 | |
|   ];
 | |
|   return (
 | |
|     <ErrorBoundary
 | |
|       // eslint-disable-next-line react/no-unstable-nested-components
 | |
|       fallbackRender={({ error, resetErrorBoundary }) => (
 | |
|         <ComponentError
 | |
|           componentName="ChatModerationDetailsModal"
 | |
|           message={error.message}
 | |
|           retryFunction={resetErrorBoundary}
 | |
|         />
 | |
|       )}
 | |
|     >
 | |
|       <Spin spinning={loading}>
 | |
|         <UserColorBlock color={displayColor} />
 | |
|         {scopes?.map(scope => <Tag key={scope}>{scope}</Tag>)}
 | |
|         {authenticated && <Tag>Authenticated</Tag>}
 | |
|         {isBot && <Tag>Bot</Tag>}
 | |
|         <ValueRow label="Messages Sent Across Clients" value={totalMessagesSent.toString()} />
 | |
|         <ValueRow label="User Created" value={createdAtDate} />
 | |
|         <ValueRow label="Known As" value={previousNames.join(',')} />
 | |
|         <Collapse accordion>
 | |
|           <Panel header="Currently Connected Clients" key="connected-clients">
 | |
|             <Collapse accordion>
 | |
|               {connectedClients.map(client => (
 | |
|                 <Panel header={formatUAstring(client.userAgent)} key={client.id}>
 | |
|                   <ConnectedClient client={client} />
 | |
|                 </Panel>
 | |
|               ))}
 | |
|             </Collapse>
 | |
|           </Panel>
 | |
|           <Collapse accordion>
 | |
|             <Panel header="Recent Chat Messages" key="chat-messages">
 | |
|               <Table
 | |
|                 size="small"
 | |
|                 pagination={null}
 | |
|                 columns={chatMessageColumns}
 | |
|                 dataSource={messages}
 | |
|                 rowKey="id"
 | |
|               />
 | |
|             </Panel>
 | |
|           </Collapse>
 | |
|         </Collapse>
 | |
|       </Spin>
 | |
|     </ErrorBoundary>
 | |
|   );
 | |
| };
 |