import React, { useCallback, 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 type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; import PermMedia from '@mui/icons-material/PermMedia'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Typography from '@mui/material/Typography'; import { DataGrid, type GridColDef } from '@mui/x-data-grid'; import { Link, useSearchParams } from 'react-router-dom'; import Page from 'components/Page'; import UserAvatar from 'components/UserAvatar'; import { useApi } from 'hooks/useApi'; import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime'; import globalize from 'lib/globalize'; import { toBoolean } from 'utils/string'; import LogLevelChip from '../components/activityTable/LogLevelChip'; import OverviewCell from '../components/activityTable/OverviewCell'; import GridActionsCellLink from '../components/dataGrid/GridActionsCellLink'; const DEFAULT_PAGE_SIZE = 25; 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; }; const getRowId = (row: ActivityLogEntry) => row.Id ?? -1; const Activity = () => { const { api } = useApi(); const [ searchParams, setSearchParams ] = useSearchParams(); const [ activityView, setActivityView ] = useState( getActivityView(searchParams.get(VIEW_PARAM))); const [ isLoading, setIsLoading ] = useState(true); const [ paginationModel, setPaginationModel ] = useState({ page: 0, pageSize: DEFAULT_PAGE_SIZE }); const [ rowCount, setRowCount ] = useState(0); const [ rows, setRows ] = useState([]); const [ users, setUsers ] = useState>({}); const userColDef: GridColDef[] = activityView !== ActivityView.System ? [ { field: 'User', headerName: globalize.translate('LabelUser'), width: 60, valueGetter: ( value, row ) => users[row.UserId]?.Name, renderCell: ({ row }) => ( ) } ] : []; const columns: GridColDef[] = [ { field: 'Date', headerName: globalize.translate('LabelDate'), width: 90, type: 'date', valueGetter: ( value ) => parseISO8601Date(value), valueFormatter: ( value ) => toLocaleDateString(value) }, { field: 'Time', headerName: globalize.translate('LabelTime'), width: 100, type: 'dateTime', valueGetter: ( value, row ) => parseISO8601Date(row.Date), valueFormatter: ( value ) => toLocaleTimeString(value) }, { field: 'Severity', headerName: globalize.translate('LabelLevel'), width: 110, renderCell: ({ value }) => ( value ? ( ) : undefined ) }, ...userColDef, { field: 'Name', headerName: globalize.translate('LabelName'), width: 300 }, { field: 'Overview', headerName: globalize.translate('LabelOverview'), width: 200, valueGetter: ( value, row ) => row.ShortOverview ?? row.Overview, renderCell: ({ row }) => ( ) }, { field: 'Type', headerName: globalize.translate('LabelType'), width: 180 }, { field: 'actions', type: 'actions', width: 50, getActions: ({ row }) => { const actions = []; if (row.ItemId) { actions.push( } label={globalize.translate('LabelMediaDetails')} title={globalize.translate('LabelMediaDetails')} to={`/details?id=${row.ItemId}`} /> ); } return actions; } } ]; const onViewChange = useCallback((_e: React.MouseEvent, newView: ActivityView | null) => { if (newView !== null) { setActivityView(newView); } }, []); useEffect(() => { if (api) { const fetchUsers = async () => { const { data } = await getUserApi(api).getUsers(); const usersById: Record = {}; 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 params: { startIndex: number, limit: number, hasUserId?: boolean } = { startIndex: paginationModel.page * paginationModel.pageSize, limit: paginationModel.pageSize }; if (activityView !== ActivityView.All) { params.hasUserId = activityView === ActivityView.User; } const { data } = await getActivityLogApi(api) .getLogEntries(params); setRowCount(data.TotalRecordCount ?? 0); setRows(data.Items ?? []); setIsLoading(false); }; setIsLoading(true); fetchActivity() .catch(err => { console.error('[activity] failed to fetch activity log entries', err); }); } }, [ activityView, api, paginationModel ]); 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 ]); return (
{globalize.translate('HeaderActivity')} {globalize.translate('All')} {globalize.translate('LabelUser')} {globalize.translate('LabelSystem')}
); }; export default Activity;