feat: admin 增加dashboard

pull/70/head
moonrailgun 2 years ago
parent 4f51ec4aa8
commit 867fbd3223

@ -921,6 +921,7 @@ importers:
react: ^18.2.0
react-admin: ^4.6.3
react-dom: ^18.2.0
react-router-dom: ^6.5.0
ts-node: ^10.9.1
typescript: ^4.8.4
dependencies:
@ -937,10 +938,11 @@ importers:
isbot: 3.6.5
lodash: 4.17.21
morgan: 1.10.0
ra-data-json-server: 4.6.3_biqbaboplfbrettd7655fr4n2y
ra-data-json-server: 4.6.3_7gebip3sgfc5uoqbhvi5xascza
react: 18.2.0
react-admin: 4.6.3_ib3m5ricvtkl2cll7qpr2f6lvq
react-dom: 18.2.0_react@18.2.0
react-router-dom: 6.5.0_biqbaboplfbrettd7655fr4n2y
ts-node: 10.9.1_typescript@4.9.4
devDependencies:
'@remix-run/dev': 1.9.0_biqbaboplfbrettd7655fr4n2y
@ -31066,7 +31068,7 @@ packages:
quill-delta: 3.6.3
dev: false
/ra-core/4.6.3_biqbaboplfbrettd7655fr4n2y:
/ra-core/4.6.3_7gebip3sgfc5uoqbhvi5xascza:
resolution: {integrity: sha512-++UW1HJDy1m5EG8/6nRNr8btHxb7I5e5dMJXo83h0E7g5st2U6bIR0XLzIdcirOy5k3h3rM0lZP+tEA9M/Oxyw==}
peerDependencies:
history: ^5.1.0
@ -31088,6 +31090,7 @@ packages:
react-dom: 18.2.0_react@18.2.0
react-is: 17.0.2
react-query: 3.39.2_biqbaboplfbrettd7655fr4n2y
react-router-dom: 6.5.0_biqbaboplfbrettd7655fr4n2y
transitivePeerDependencies:
- react-native
dev: false
@ -31122,11 +31125,11 @@ packages:
- react-native
dev: false
/ra-data-json-server/4.6.3_biqbaboplfbrettd7655fr4n2y:
/ra-data-json-server/4.6.3_7gebip3sgfc5uoqbhvi5xascza:
resolution: {integrity: sha512-KChdLr/quUimzzsl5XTBc6jGg7EvQgjlRFi4Ymk8ovD/I11m2Vz8iXwgAV2kWDMKPqFuaRC9/xd6XZwjD/5zQQ==}
dependencies:
query-string: 7.1.3
ra-core: 4.6.3_biqbaboplfbrettd7655fr4n2y
ra-core: 4.6.3_7gebip3sgfc5uoqbhvi5xascza
transitivePeerDependencies:
- history
- react

@ -18,6 +18,7 @@ import MessageIcon from '@mui/icons-material/Message';
import GroupIcon from '@mui/icons-material/Group';
import AttachFileIcon from '@mui/icons-material/AttachFile';
import { theme } from './theme';
import { Dashboard } from './dashboard';
const httpClient: typeof fetchUtils.fetchJson = (url, options = {}) => {
try {
@ -43,6 +44,8 @@ export const App = () => (
<Admin
basename="/admin"
theme={theme}
dashboard={Dashboard}
disableTelemetry={true}
authProvider={authProvider}
dataProvider={dataProvider}
>

@ -0,0 +1,70 @@
import * as React from 'react';
import { FC, createElement } from 'react';
import { Card, Box, Typography, Divider } from '@mui/material';
import { Link, To } from 'react-router-dom';
import { ReactNode } from 'react';
import { LoadingIndicator } from 'react-admin';
import cartouche from './cartouche.png';
import cartoucheDark from './cartoucheDark.png';
interface Props {
icon: FC<any>;
to: To;
title?: string;
subtitle?: string | number;
children?: ReactNode;
}
const CardWithIcon = (props: Props) => {
const { icon, title, subtitle, to, children } = props;
return (
<Card
sx={{
minHeight: 52,
display: 'flex',
flexDirection: 'column',
flex: '1',
'& a': {
textDecoration: 'none',
color: 'inherit',
},
}}
>
<Link to={to}>
<Box
sx={{
overflow: 'inherit',
padding: '16px',
background: (theme) =>
`url(${
theme.palette.mode === 'dark' ? cartoucheDark : cartouche
}) no-repeat`,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
'& .icon': {
color: (theme) =>
theme.palette.mode === 'dark' ? 'inherit' : '#dc2440',
},
}}
>
<Box width="3em" className="icon">
{createElement(icon, { fontSize: 'large' })}
</Box>
<Box textAlign="right">
<Typography color="textSecondary">{title}</Typography>
<Typography variant="h5" component="h2">
{subtitle || <LoadingIndicator />}
</Typography>
</Box>
</Box>
</Link>
{children && <Divider />}
{children}
</Card>
);
};
export default CardWithIcon;

@ -0,0 +1,77 @@
import React from 'react';
import { Card, Box, Typography, CardActions, Button } from '@mui/material';
import HomeIcon from '@mui/icons-material/Home';
import CodeIcon from '@mui/icons-material/Code';
import logoSvg from './logo.svg';
export const Welcome: React.FC = React.memo(() => {
return (
<Card
sx={{
background: (theme) =>
theme.palette.mode === 'dark'
? '#535353'
: `linear-gradient(to right, #1a1d26 0%, #232c50 35%)`,
color: '#fff',
padding: '20px',
marginTop: 2,
marginBottom: '1em',
}}
>
<Box display="flex">
<Box flex="1">
<Typography variant="h5" component="h2" gutterBottom>
使 Tailchat
</Typography>
<Box maxWidth="40em">
<Typography variant="body1" component="p" gutterBottom>
Tailchat
</Typography>
</Box>
<CardActions
sx={{
padding: { xs: 0, xl: null },
flexWrap: { xs: 'wrap', xl: null },
'& a': {
marginTop: { xs: '1em', xl: null },
marginLeft: { xs: '0!important', xl: null },
marginRight: { xs: '1em', xl: null },
},
}}
>
<Button
variant="contained"
href="https://tailchat.msgbyte.com/"
startIcon={<HomeIcon />}
target="__blank"
>
访
</Button>
<Button
variant="contained"
href="https://github.com/msgbyte/tailchat"
startIcon={<CodeIcon />}
target="__blank"
>
</Button>
</CardActions>
</Box>
<Box
display={{ xs: 'none', sm: 'none', md: 'block' }}
sx={{
marginLeft: 'auto',
backgroundImage: `url(${logoSvg})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
}}
width="9em"
height="9em"
overflow="hidden"
/>
</Box>
</Card>
);
});
Welcome.displayName = 'Welcome';

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,78 @@
import React from 'react';
import CardWithIcon from './CardWithIcon';
import { Welcome } from './Welcome';
import PersonIcon from '@mui/icons-material/Person';
import MessageIcon from '@mui/icons-material/Message';
import GroupIcon from '@mui/icons-material/Group';
import AttachFileIcon from '@mui/icons-material/AttachFile';
import { useGetList } from 'react-admin';
import { Grid } from '@mui/material';
export const Dashboard: React.FC = React.memo(() => {
const { total: usersNum } = useGetList('users', {
pagination: { page: 1, perPage: 1 },
});
const { total: tempUsersNum } = useGetList('users', {
filter: { temporary: true },
pagination: { page: 1, perPage: 1 },
});
const { total: messageNum } = useGetList('messages', {
pagination: { page: 1, perPage: 1 },
});
const { total: groupNum } = useGetList('groups', {
pagination: { page: 1, perPage: 1 },
});
const { total: fileNum } = useGetList('file', {
pagination: { page: 1, perPage: 1 },
});
return (
<div>
<Welcome />
<Grid container spacing={2}>
<Grid item xs={4}>
<CardWithIcon
to="/admin/users"
icon={PersonIcon}
title={'用户数'}
subtitle={usersNum}
/>
</Grid>
<Grid item xs={4}>
<CardWithIcon
to="/admin/users"
icon={PersonIcon}
title={'临时用户数'}
subtitle={tempUsersNum}
/>
</Grid>
<Grid item xs={4}>
<CardWithIcon
to="/admin/messages"
icon={MessageIcon}
title={'总消息数'}
subtitle={messageNum}
/>
</Grid>
<Grid item xs={4}>
<CardWithIcon
to="/admin/groups"
icon={GroupIcon}
title={'总群组数'}
subtitle={groupNum}
/>
</Grid>
<Grid item xs={4}>
<CardWithIcon
to="/admin/file"
icon={AttachFileIcon}
title={'总文件数'}
subtitle={fileNum}
/>
</Grid>
</Grid>
</div>
);
});
Dashboard.displayName = 'Dashboard';

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

@ -29,11 +29,7 @@ export const GroupList: React.FC = () => (
<TextField source="name" label="群组名称" />
<TextField source="owner" label="管理员" />
<TextField source="members.length" label="成员数量" />
<ArrayField source="panels" label="面板">
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ArrayField>
<TextField source="panels.length" label="面板数量" />
<ArrayField source="roles" label="角色">
<SingleFieldList>
<ChipField source="name" />

@ -28,6 +28,7 @@
"react": "^18.2.0",
"react-admin": "^4.6.3",
"react-dom": "^18.2.0",
"react-router-dom": "^6.5.0",
"ts-node": "^10.9.1"
},
"devDependencies": {

Loading…
Cancel
Save