mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Rewrite activity log page in dashboard
This commit is contained in:
parent
13aa3c9efa
commit
3bad9f0378
8 changed files with 272 additions and 16 deletions
|
@ -1,6 +1,4 @@
|
|||
import Avatar from '@mui/material/Avatar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
|
@ -8,10 +6,10 @@ import { useApi } from 'hooks/useApi';
|
|||
import globalize from 'scripts/globalize';
|
||||
|
||||
import AppUserMenu, { ID } from './menus/AppUserMenu';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
|
||||
const UserMenuButton = () => {
|
||||
const theme = useTheme();
|
||||
const { api, user } = useApi();
|
||||
const { user } = useApi();
|
||||
|
||||
const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState<null | HTMLElement>(null);
|
||||
const isUserMenuOpen = Boolean(userMenuAnchorEl);
|
||||
|
@ -37,18 +35,7 @@ const UserMenuButton = () => {
|
|||
color='inherit'
|
||||
sx={{ padding: 0 }}
|
||||
>
|
||||
<Avatar
|
||||
alt={user?.Name || undefined}
|
||||
src={
|
||||
api && user?.Id ?
|
||||
`${api.basePath}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}` :
|
||||
undefined
|
||||
}
|
||||
sx={{
|
||||
bgcolor: theme.palette.primary.dark,
|
||||
color: 'inherit'
|
||||
}}
|
||||
/>
|
||||
<UserAvatar user={user} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
||||
|
||||
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
||||
{ path: 'dashboard/activity', page: 'dashboard/activity' },
|
||||
{ path: 'notificationsettings.html', page: 'dashboard/notifications' },
|
||||
{ path: 'usernew.html', page: 'user/usernew' },
|
||||
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
||||
|
||||
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
||||
{ path: 'dashboard/activity', page: 'dashboard/activity' },
|
||||
{ path: 'notificationsettings.html', page: 'dashboard/notifications' },
|
||||
{ path: 'usernew.html', page: 'user/usernew' },
|
||||
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
||||
|
|
175
src/apps/stable/routes/dashboard/activity.tsx
Normal file
175
src/apps/stable/routes/dashboard/activity.tsx
Normal file
|
@ -0,0 +1,175 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
||||
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
||||
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
|
||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import UserAvatar from 'components/UserAvatar';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 25;
|
||||
|
||||
const getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
|
||||
|
||||
const LogLevelChip = ({ level }: { level: LogLevel }) => {
|
||||
let color: 'info' | 'warning' | 'error' | undefined = undefined;
|
||||
switch (level) {
|
||||
case LogLevel.Information:
|
||||
color = 'info';
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
color = 'warning';
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Critical:
|
||||
color = 'error';
|
||||
break;
|
||||
}
|
||||
|
||||
const levelText = globalize.translate(`LogLevel.${level}`);
|
||||
|
||||
return (
|
||||
<Chip
|
||||
size='small'
|
||||
color={color}
|
||||
label={levelText}
|
||||
title={levelText}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Activity = () => {
|
||||
const { api } = useApi();
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: 'Date',
|
||||
headerName: globalize.translate('LabelDate'),
|
||||
width: 180,
|
||||
type: 'dateTime',
|
||||
valueGetter: ({ row }) => new Date(row.Date)
|
||||
},
|
||||
{
|
||||
field: 'Severity',
|
||||
headerName: globalize.translate('LabelLevel'),
|
||||
width: 110,
|
||||
renderCell: ({ row }) => (
|
||||
row.Severity ? (
|
||||
<LogLevelChip level={row.Severity} />
|
||||
) : undefined
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'User',
|
||||
headerName: globalize.translate('LabelUser'),
|
||||
width: 60,
|
||||
valueGetter: ({ row }) => users[row.UserId]?.Name,
|
||||
renderCell: ({ row }) => (
|
||||
<UserAvatar
|
||||
user={users[row.UserId]}
|
||||
showTitle
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'Name',
|
||||
headerName: globalize.translate('LabelName'),
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
field: 'Overview',
|
||||
headerName: globalize.translate('LabelOverview'),
|
||||
width: 200,
|
||||
valueGetter: ({ row }) => row.Overview ?? row.ShortOverview
|
||||
},
|
||||
{
|
||||
field: 'Type',
|
||||
headerName: globalize.translate('LabelType'),
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
const [ isLoading, setIsLoading ] = useState(true);
|
||||
const [paginationModel, setPaginationModel] = useState({
|
||||
page: 0,
|
||||
pageSize: DEFAULT_PAGE_SIZE
|
||||
});
|
||||
const [ rowCount, setRowCount ] = useState(0);
|
||||
const [ rows, setRows ] = useState<ActivityLogEntry[]>([]);
|
||||
const [ users, setUsers ] = useState<Record<string, UserDto>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (api) {
|
||||
const fetchUsers = async () => {
|
||||
const { data } = await getUserApi(api).getUsers();
|
||||
const usersById: Record<string, UserDto> = {};
|
||||
data.forEach(user => {
|
||||
if (user.Id) {
|
||||
usersById[user.Id] = user;
|
||||
}
|
||||
});
|
||||
|
||||
setUsers(usersById);
|
||||
};
|
||||
|
||||
fetchUsers()
|
||||
.catch(err => {
|
||||
console.error('[activity] failed to fetch users', err);
|
||||
});
|
||||
}
|
||||
}, [ api ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (api) {
|
||||
const fetchActivity = async () => {
|
||||
const { data } = await getActivityLogApi(api)
|
||||
.getLogEntries({
|
||||
startIndex: paginationModel.page * paginationModel.pageSize,
|
||||
limit: paginationModel.pageSize
|
||||
});
|
||||
|
||||
setRowCount(data.TotalRecordCount ?? 0);
|
||||
setRows(data.Items ?? []);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
fetchActivity()
|
||||
.catch(err => {
|
||||
console.error('[activity] failed to fetch activity log entries', err);
|
||||
});
|
||||
}
|
||||
}, [ api, paginationModel ]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
id='serverActivityPage'
|
||||
title={globalize.translate('HeaderActivity')}
|
||||
className='mainAnimatedPage type-interior'
|
||||
>
|
||||
<div className='content-primary'>
|
||||
<h2>{globalize.translate('HeaderActivity')}</h2>
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
rows={rows}
|
||||
pageSizeOptions={[ 10, 25, 50, 100 ]}
|
||||
paginationMode='server'
|
||||
paginationModel={paginationModel}
|
||||
onPaginationModelChange={setPaginationModel}
|
||||
rowCount={rowCount}
|
||||
getRowId={getRowId}
|
||||
loading={isLoading}
|
||||
sx={{
|
||||
minHeight: 500
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Activity;
|
Loading…
Add table
Add a link
Reference in a new issue