mirror of https://github.com/msgbyte/tailchat
feat(admin-next): add custom routes
- SystemConfig - TailchatNetwork - SocketIOAdminpull/90/head
parent
d68108b21a
commit
ef88363bbf
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { styled, Tag } from 'tushan';
|
||||||
|
|
||||||
|
const Root = styled.div`
|
||||||
|
* {
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TagItems: React.FC<{
|
||||||
|
items: string[];
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
{props.items.map((item, i) => (
|
||||||
|
<Tag key={i}>{item}</Tag>
|
||||||
|
))}
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TagItems.displayName = 'TagItems';
|
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { parseUrlStr } from '../utils';
|
||||||
|
import { Image as OriginImage, ImageProps, styled } from 'tushan';
|
||||||
|
|
||||||
|
const Image = styled(OriginImage)`
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TailchatImage: React.FC<ImageProps> = React.memo((props) => {
|
||||||
|
return <Image {...props} src={parseUrlStr(props.src ?? '')} />;
|
||||||
|
});
|
||||||
|
TailchatImage.displayName = 'TailchatImage';
|
@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { request } from '../../request';
|
||||||
|
import _uniq from 'lodash/uniq';
|
||||||
|
import { TagItems } from '../../components/TagItems';
|
||||||
|
import { Card, Spin, Table, Typography, useAsync } from 'tushan';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailchat 网络状态
|
||||||
|
*/
|
||||||
|
export const TailchatNetwork: React.FC = React.memo(() => {
|
||||||
|
const { value: data, loading } = useAsync(async () => {
|
||||||
|
const { data } = await request('/network/all');
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Typography.Title heading={6}>
|
||||||
|
{'custom.network.nodeList'}
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
dataIndex: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
render: (id, item: any) => (
|
||||||
|
<div>
|
||||||
|
{id}
|
||||||
|
{item.local && <span> (*)</span>}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'hostname',
|
||||||
|
title: 'Host',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'cpu',
|
||||||
|
title: 'CPU',
|
||||||
|
render: (usage) => usage + '%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'ipList',
|
||||||
|
title: 'IP',
|
||||||
|
render: (ips) => <TagItems items={ips ?? []} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'client.version',
|
||||||
|
title: 'Client Version',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
data={data.nodes ?? []}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Title heading={6}>
|
||||||
|
{'custom.network.serviceList'}
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TagItems items={_uniq<string>(data.services ?? [])} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Typography.Title heading={6}>
|
||||||
|
{'custom.network.actionList'}
|
||||||
|
</Typography.Title>
|
||||||
|
<div>
|
||||||
|
<TagItems items={_uniq<string>(data.actions ?? [])} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Typography.Title heading={6}>
|
||||||
|
{'custom.network.eventList'}
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TagItems items={_uniq<string>(data.events ?? [])} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TailchatNetwork.displayName = 'TailchatNetwork';
|
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Card, Typography } from 'tushan';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SocketIO 管理
|
||||||
|
*/
|
||||||
|
export const SocketIOAdmin: React.FC = React.memo(() => {
|
||||||
|
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<div>
|
||||||
|
<Typography.Paragraph>
|
||||||
|
{'custom.socketio.tip1'}{' '}
|
||||||
|
<strong>
|
||||||
|
{protocol}://{window.location.host}
|
||||||
|
</strong>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<Typography.Paragraph>{'custom.socketio.tip2'}</Typography.Paragraph>
|
||||||
|
<Typography.Paragraph>{'custom.socketio.tip3'}</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
window.open('https://admin.socket.io/');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{'custom.socketio.btn'}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
SocketIOAdmin.displayName = 'SocketIOAdmin';
|
@ -0,0 +1,147 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { request } from '../../request';
|
||||||
|
import {
|
||||||
|
useAsyncRequest,
|
||||||
|
useEditValue,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Spin,
|
||||||
|
Message,
|
||||||
|
Form,
|
||||||
|
Upload,
|
||||||
|
} from 'tushan';
|
||||||
|
import { IconCheck, IconClose, IconDelete } from 'tushan/icon';
|
||||||
|
import { TailchatImage } from '../../components/TailchatImage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tailchat 系统设置
|
||||||
|
*/
|
||||||
|
export const SystemConfig: React.FC = React.memo(() => {
|
||||||
|
const [{ value: config = {}, loading }, fetchConfig] = useAsyncRequest(
|
||||||
|
async () => {
|
||||||
|
const { data } = await request.get('/config/client');
|
||||||
|
|
||||||
|
return data.config ?? {};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [serverName, setServerName, saveServerName] = useEditValue(
|
||||||
|
config?.serverName,
|
||||||
|
async (val) => {
|
||||||
|
if (val === config?.serverName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.patch('/config/client', {
|
||||||
|
key: 'serverName',
|
||||||
|
value: val,
|
||||||
|
});
|
||||||
|
fetchConfig();
|
||||||
|
Message.success('Success');
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Message.error(String(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [{}, handleChangeServerEntryImage] = useAsyncRequest(
|
||||||
|
async (file: File | null) => {
|
||||||
|
if (file) {
|
||||||
|
const formdata = new FormData();
|
||||||
|
formdata.append('file', file);
|
||||||
|
|
||||||
|
const { data } = await request.put('/file/upload', formdata, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileInfo = data.files[0];
|
||||||
|
|
||||||
|
if (!fileInfo) {
|
||||||
|
throw new Error('not get file');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = fileInfo.url;
|
||||||
|
await request.patch('/config/client', {
|
||||||
|
key: 'serverEntryImage',
|
||||||
|
value: url,
|
||||||
|
});
|
||||||
|
fetchConfig();
|
||||||
|
} else {
|
||||||
|
// delete
|
||||||
|
await request.patch('/config/client', {
|
||||||
|
key: 'serverEntryImage',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
fetchConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<Form.Item label={'custom.config.uploadFileLimit'}>
|
||||||
|
{config.uploadFileLimit}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={'custom.config.emailVerification'}>
|
||||||
|
{config.emailVerification ? <IconCheck /> : <IconClose />}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={'custom.config.serverName'}>
|
||||||
|
<Input
|
||||||
|
value={serverName}
|
||||||
|
onChange={(val) => setServerName(val)}
|
||||||
|
onBlur={() => saveServerName()}
|
||||||
|
placeholder="Tailchat"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={'custom.config.serverEntryImage'}>
|
||||||
|
<div>
|
||||||
|
{config?.serverEntryImage ? (
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<div>
|
||||||
|
<TailchatImage
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: 360,
|
||||||
|
overflow: 'hidden',
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
src={config?.serverEntryImage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<IconDelete />}
|
||||||
|
onClick={() => handleChangeServerEntryImage(null)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Upload
|
||||||
|
onChange={(_, file) => {
|
||||||
|
handleChangeServerEntryImage(file.originFile);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
SystemConfig.displayName = 'SystemConfig';
|
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* parse url, and replace some constants with variable
|
||||||
|
* @param originUrl 原始Url
|
||||||
|
* @returns 解析后的url
|
||||||
|
*/
|
||||||
|
export function parseUrlStr(originUrl: string): string {
|
||||||
|
return String(originUrl).replace(
|
||||||
|
'{BACKEND}',
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? 'http://localhost:11000'
|
||||||
|
: window.location.origin
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue