1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
jellyfin-web/src/apps/dashboard/routes/activity.tsx

270 lines
9.2 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useEffect, useMemo, useState } from 'react';
2024-08-26 17:10:35 -04:00
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
2023-06-04 02:34:40 -04:00
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
2024-08-26 17:10:35 -04:00
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
2023-06-04 02:34:40 -04:00
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import PermMedia from '@mui/icons-material/PermMedia';
2023-06-09 01:20:34 -04:00
import Box from '@mui/material/Box';
2023-06-10 02:31:48 -04:00
import IconButton from '@mui/material/IconButton';
2023-06-09 01:20:34 -04:00
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Typography from '@mui/material/Typography';
2024-08-26 17:10:35 -04:00
import { type MRT_Cell, type MRT_ColumnDef, type MRT_Row, MaterialReactTable, useMaterialReactTable } from 'material-react-table';
import { Link, useSearchParams } from 'react-router-dom';
2023-06-04 02:34:40 -04:00
import Page from 'components/Page';
import UserAvatar from 'components/UserAvatar';
import { useLogEntires } from 'hooks/useLogEntries';
import { useUsers } from 'hooks/useUsers';
2024-08-26 17:10:35 -04:00
import { parseISO8601Date, toLocaleString } from 'scripts/datetime';
2024-08-14 13:31:34 -04:00
import globalize from 'lib/globalize';
2023-06-09 01:20:34 -04:00
import { toBoolean } from 'utils/string';
2023-06-04 02:34:40 -04:00
2023-09-20 16:25:11 -04:00
import LogLevelChip from '../components/activityTable/LogLevelChip';
import OverviewCell from '../components/activityTable/OverviewCell';
2023-06-04 02:34:40 -04:00
const DEFAULT_PAGE_SIZE = 25;
2023-06-09 01:20:34 -04:00
const VIEW_PARAM = 'useractivity';
const enum ActivityView {
All,
User,
System
}
const getActivityView = (param: string | null) => {
if (param === null) return ActivityView.All;
if (toBoolean(param)) return ActivityView.User;
return ActivityView.System;
};
2023-06-04 02:34:40 -04:00
const Activity = () => {
2023-06-09 01:20:34 -04:00
const [ searchParams, setSearchParams ] = useSearchParams();
2023-06-04 02:34:40 -04:00
2023-06-10 02:31:48 -04:00
const [ activityView, setActivityView ] = useState(
getActivityView(searchParams.get(VIEW_PARAM)));
2024-08-26 17:10:35 -04:00
const [ pagination, setPagination ] = useState({
pageIndex: 0,
2023-06-10 02:31:48 -04:00
pageSize: DEFAULT_PAGE_SIZE
});
const { data: usersData, isLoading: isUsersLoading } = useUsers();
type UsersRecords = Record<string, UserDto>;
const users: UsersRecords = useMemo(() => {
if (!usersData) return {};
return usersData.reduce<UsersRecords>((acc, user) => {
const userId = user.Id;
if (!userId) return acc;
return {
...acc,
[userId]: user
};
}, {});
}, [usersData]);
const activityParams = useMemo(() => ({
2024-08-26 17:10:35 -04:00
startIndex: pagination.pageIndex * pagination.pageSize,
limit: pagination.pageSize,
hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined
2024-08-26 17:10:35 -04:00
}), [activityView, pagination.pageIndex, pagination.pageSize]);
const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntires(activityParams);
const isLoading = isUsersLoading || isLogEntriesLoading;
2023-06-10 02:31:48 -04:00
2024-08-26 17:10:35 -04:00
const columns = useMemo<MRT_ColumnDef<ActivityLogEntry>[]>(() => [
2023-06-10 02:31:48 -04:00
{
2024-08-26 17:10:35 -04:00
id: 'Date',
accessorFn: row => parseISO8601Date(row.Date),
header: globalize.translate('LabelTime'),
size: 160,
Cell: ({ cell }) => toLocaleString(cell.getValue<Date>())
2023-06-10 02:31:48 -04:00
},
{
2024-08-26 17:10:35 -04:00
accessorKey: 'Severity',
header: globalize.translate('LabelLevel'),
size: 90,
Cell: ({ cell }: { cell: MRT_Cell<ActivityLogEntry> }) => (
cell.getValue<LogLevel | undefined>() ? (
<LogLevelChip level={cell.getValue<LogLevel>()} />
) : undefined
),
enableResizing: false,
muiTableBodyCellProps: {
align: 'center'
}
2023-06-04 02:34:40 -04:00
},
{
2024-08-26 17:10:35 -04:00
id: 'User',
accessorFn: row => row.UserId && users[row.UserId]?.Name,
header: globalize.translate('LabelUser'),
size: 75,
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
row.original.UserId ? (
<IconButton
size='large'
color='inherit'
sx={{ padding: 0 }}
title={users[row.original.UserId]?.Name || undefined}
component={Link}
to={`/dashboard/users/profile?userId=${row.original.UserId}`}
>
<UserAvatar user={users[row.original.UserId]} />
</IconButton>
2023-06-04 02:34:40 -04:00
) : undefined
2024-08-26 17:10:35 -04:00
),
enableResizing: false,
visibleInShowHideMenu: activityView !== ActivityView.System,
muiTableBodyCellProps: {
align: 'center'
}
2023-06-04 02:34:40 -04:00
},
{
2024-08-26 17:10:35 -04:00
accessorKey: 'Name',
header: globalize.translate('LabelName'),
size: 270
2023-06-04 02:34:40 -04:00
},
{
2024-08-26 17:10:35 -04:00
id: 'Overview',
accessorFn: row => row.ShortOverview || row.Overview,
header: globalize.translate('LabelOverview'),
size: 170,
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
<OverviewCell {...row.original} />
2023-06-10 02:31:48 -04:00
)
2023-06-04 02:34:40 -04:00
},
{
2024-08-26 17:10:35 -04:00
accessorKey: 'Type',
header: globalize.translate('LabelType'),
size: 150
},
{
2024-08-26 17:10:35 -04:00
id: 'Actions',
accessorFn: row => row.ItemId,
header: '',
size: 60,
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
row.original.ItemId ? (
<IconButton
size='large'
title={globalize.translate('LabelMediaDetails')}
component={Link}
to={`/details?id=${row.original.ItemId}`}
>
<PermMedia fontSize='inherit' />
</IconButton>
) : undefined
),
enableColumnActions: false,
enableColumnFilter: false,
enableResizing: false,
enableSorting: false
2023-06-04 02:34:40 -04:00
}
2024-08-26 17:10:35 -04:00
], [ activityView, users ]);
2023-06-04 02:34:40 -04:00
2024-06-02 20:58:11 +03:00
const onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
2023-06-09 01:20:34 -04:00
if (newView !== null) {
setActivityView(newView);
}
}, []);
useEffect(() => {
const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM));
if (currentViewParam !== activityView) {
if (activityView === ActivityView.All) {
searchParams.delete(VIEW_PARAM);
} else {
searchParams.set(VIEW_PARAM, `${activityView === ActivityView.User}`);
}
setSearchParams(searchParams);
}
}, [ activityView, searchParams, setSearchParams ]);
2023-06-04 02:34:40 -04:00
2024-08-26 17:10:35 -04:00
const table = useMaterialReactTable({
columns,
data: logEntries?.Items || [],
// Enable custom features
enableColumnPinning: true,
enableColumnResizing: true,
// Sticky header/footer
enableStickyFooter: true,
enableStickyHeader: true,
muiTableContainerProps: {
sx: {
maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer
}
},
// State
state: {
isLoading,
pagination,
columnVisibility: {
User: activityView !== ActivityView.System
}
},
// Server pagination
manualPagination: true,
onPaginationChange: setPagination,
rowCount: logEntries?.TotalRecordCount || 0,
// Custom toolbar contents
renderTopToolbarCustomActions: () => (
<ToggleButtonGroup
size='small'
value={activityView}
onChange={onViewChange}
exclusive
>
<ToggleButton value={ActivityView.All}>
{globalize.translate('All')}
</ToggleButton>
<ToggleButton value={ActivityView.User}>
{globalize.translate('LabelUser')}
</ToggleButton>
<ToggleButton value={ActivityView.System}>
{globalize.translate('LabelSystem')}
</ToggleButton>
</ToggleButtonGroup>
)
});
2023-06-04 02:34:40 -04:00
return (
<Page
id='serverActivityPage'
title={globalize.translate('HeaderActivity')}
className='mainAnimatedPage type-interior'
>
2024-08-21 12:52:45 -04:00
<Box
className='content-primary'
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
2023-06-09 01:20:34 -04:00
<Box
sx={{
2024-08-26 17:10:35 -04:00
marginBottom: 1
2023-06-09 01:20:34 -04:00
}}
>
2024-08-26 17:10:35 -04:00
<Typography variant='h2'>
{globalize.translate('HeaderActivity')}
</Typography>
2023-06-09 01:20:34 -04:00
</Box>
2024-08-26 17:10:35 -04:00
<MaterialReactTable table={table} />
2024-08-21 12:52:45 -04:00
</Box>
2023-06-04 02:34:40 -04:00
</Page>
);
};
export default Activity;