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

249 lines
8.6 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useEffect, useMemo, useState } from 'react';
2023-06-04 02:34:40 -04:00
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
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';
2023-06-04 02:34:40 -04:00
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
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';
2023-06-10 02:31:48 -04:00
import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } 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';
import GridActionsCellLink from '../components/dataGrid/GridActionsCellLink';
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 getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
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)));
2023-06-10 02:31:48 -04:00
const [ paginationModel, setPaginationModel ] = useState({
page: 0,
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(() => ({
startIndex: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize,
hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined
}), [activityView, paginationModel.page, paginationModel.pageSize]);
const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntires(activityParams);
const isLoading = isUsersLoading || isLogEntriesLoading;
2023-06-10 02:31:48 -04:00
const userColDef: GridColDef[] = activityView !== ActivityView.System ? [
{
field: 'User',
headerName: globalize.translate('LabelUser'),
width: 60,
2024-06-13 00:30:43 +08:00
valueGetter: ( value, row ) => users[row.UserId]?.Name,
2023-06-10 02:31:48 -04:00
renderCell: ({ row }) => (
<IconButton
size='large'
color='inherit'
sx={{ padding: 0 }}
title={users[row.UserId]?.Name ?? undefined}
component={Link}
2023-09-25 00:00:36 -04:00
to={`/dashboard/users/profile?userId=${row.UserId}`}
>
<UserAvatar user={users[row.UserId]} />
</IconButton>
2023-06-10 02:31:48 -04:00
)
}
] : [];
2023-06-04 02:34:40 -04:00
const columns: GridColDef[] = [
{
field: 'Date',
headerName: globalize.translate('LabelDate'),
2023-06-10 02:31:48 -04:00
width: 90,
type: 'date',
2024-06-13 00:30:43 +08:00
valueGetter: ( value ) => parseISO8601Date(value),
valueFormatter: ( value ) => toLocaleDateString(value)
2023-06-10 02:31:48 -04:00
},
{
field: 'Time',
headerName: globalize.translate('LabelTime'),
width: 100,
2023-06-04 02:34:40 -04:00
type: 'dateTime',
2024-06-13 00:30:43 +08:00
valueGetter: ( value, row ) => parseISO8601Date(row.Date),
valueFormatter: ( value ) => toLocaleTimeString(value)
2023-06-04 02:34:40 -04:00
},
{
field: 'Severity',
headerName: globalize.translate('LabelLevel'),
width: 110,
2023-06-10 02:31:48 -04:00
renderCell: ({ value }) => (
value ? (
<LogLevelChip level={value} />
2023-06-04 02:34:40 -04:00
) : undefined
)
},
2023-06-10 02:31:48 -04:00
...userColDef,
2023-06-04 02:34:40 -04:00
{
field: 'Name',
headerName: globalize.translate('LabelName'),
width: 300
2023-06-04 02:34:40 -04:00
},
{
field: 'Overview',
headerName: globalize.translate('LabelOverview'),
width: 200,
2024-06-13 00:30:43 +08:00
valueGetter: ( value, row ) => row.ShortOverview ?? row.Overview,
2023-06-10 02:31:48 -04:00
renderCell: ({ row }) => (
<OverviewCell {...row} />
)
2023-06-04 02:34:40 -04:00
},
{
field: 'Type',
headerName: globalize.translate('LabelType'),
width: 180
},
{
field: 'actions',
type: 'actions',
width: 50,
getActions: ({ row }) => {
const actions = [];
if (row.ItemId) {
actions.push(
<GridActionsCellLink
size='large'
icon={<PermMedia />}
label={globalize.translate('LabelMediaDetails')}
title={globalize.translate('LabelMediaDetails')}
to={`/details?id=${row.ItemId}`}
/>
);
}
return actions;
}
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
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={{
display: 'flex',
alignItems: 'baseline',
marginY: 2
}}
>
<Box sx={{ flexGrow: 1 }}>
<Typography variant='h2'>
{globalize.translate('HeaderActivity')}
</Typography>
</Box>
<ToggleButtonGroup
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>
</Box>
2023-06-04 02:34:40 -04:00
<DataGrid
columns={columns}
rows={logEntries?.Items || []}
2023-06-04 02:34:40 -04:00
pageSizeOptions={[ 10, 25, 50, 100 ]}
paginationMode='server'
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
rowCount={logEntries?.TotalRecordCount || 0}
2023-06-04 02:34:40 -04:00
getRowId={getRowId}
loading={isLoading}
/>
2024-08-21 12:52:45 -04:00
</Box>
2023-06-04 02:34:40 -04:00
</Page>
);
};
export default Activity;