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 { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; 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 { useSearchParams } from 'react-router-dom'; import Page from 'components/Page'; import UserAvatar from 'components/UserAvatar'; import { useApi } from 'hooks/useApi'; import globalize from 'scripts/globalize'; import { toBoolean } from 'utils/string'; 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 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 ( ); }; const Activity = () => { const { api } = useApi(); const [ searchParams, setSearchParams ] = useSearchParams(); 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 ? ( ) : undefined ) }, { field: 'User', headerName: globalize.translate('LabelUser'), width: 60, valueGetter: ({ row }) => users[row.UserId]?.Name, renderCell: ({ row }) => ( ) }, { 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 [ 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 onViewChange = useCallback((_e, 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); }; 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;