mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add user/system event filtering
This commit is contained in:
parent
e3f605ef2d
commit
3038ddb6ff
3 changed files with 94 additions and 8 deletions
|
@ -21,6 +21,15 @@ const theme = createTheme({
|
||||||
fontFamily: '"Noto Sans", sans-serif',
|
fontFamily: '"Noto Sans", sans-serif',
|
||||||
button: {
|
button: {
|
||||||
textTransform: 'none'
|
textTransform: 'none'
|
||||||
|
},
|
||||||
|
h1: {
|
||||||
|
fontSize: '1.8rem'
|
||||||
|
},
|
||||||
|
h2: {
|
||||||
|
fontSize: '1.5rem'
|
||||||
|
},
|
||||||
|
h3: {
|
||||||
|
fontSize: '1.17rem'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,18 +1,37 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
||||||
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-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 { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
||||||
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
|
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
|
||||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
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 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 { DataGrid, type GridColDef } from '@mui/x-data-grid';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
import UserAvatar from 'components/UserAvatar';
|
import UserAvatar from 'components/UserAvatar';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import globalize from 'scripts/globalize';
|
import globalize from 'scripts/globalize';
|
||||||
|
import { toBoolean } from 'utils/string';
|
||||||
|
|
||||||
const DEFAULT_PAGE_SIZE = 25;
|
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 getRowId = (row: ActivityLogEntry) => row.Id ?? -1;
|
||||||
|
|
||||||
|
@ -45,6 +64,7 @@ const LogLevelChip = ({ level }: { level: LogLevel }) => {
|
||||||
|
|
||||||
const Activity = () => {
|
const Activity = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{
|
{
|
||||||
|
@ -94,8 +114,10 @@ const Activity = () => {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const [ activityView, setActivityView ] = useState(
|
||||||
|
getActivityView(searchParams.get(VIEW_PARAM)));
|
||||||
const [ isLoading, setIsLoading ] = useState(true);
|
const [ isLoading, setIsLoading ] = useState(true);
|
||||||
const [paginationModel, setPaginationModel] = useState({
|
const [ paginationModel, setPaginationModel ] = useState({
|
||||||
page: 0,
|
page: 0,
|
||||||
pageSize: DEFAULT_PAGE_SIZE
|
pageSize: DEFAULT_PAGE_SIZE
|
||||||
});
|
});
|
||||||
|
@ -103,6 +125,12 @@ const Activity = () => {
|
||||||
const [ rows, setRows ] = useState<ActivityLogEntry[]>([]);
|
const [ rows, setRows ] = useState<ActivityLogEntry[]>([]);
|
||||||
const [ users, setUsers ] = useState<Record<string, UserDto>>({});
|
const [ users, setUsers ] = useState<Record<string, UserDto>>({});
|
||||||
|
|
||||||
|
const onViewChange = useCallback((_e, newView: ActivityView | null) => {
|
||||||
|
if (newView !== null) {
|
||||||
|
setActivityView(newView);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (api) {
|
if (api) {
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
|
@ -127,11 +155,20 @@ const Activity = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (api) {
|
if (api) {
|
||||||
const fetchActivity = async () => {
|
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)
|
const { data } = await getActivityLogApi(api)
|
||||||
.getLogEntries({
|
.getLogEntries(params);
|
||||||
startIndex: paginationModel.page * paginationModel.pageSize,
|
|
||||||
limit: paginationModel.pageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
setRowCount(data.TotalRecordCount ?? 0);
|
setRowCount(data.TotalRecordCount ?? 0);
|
||||||
setRows(data.Items ?? []);
|
setRows(data.Items ?? []);
|
||||||
|
@ -143,7 +180,19 @@ const Activity = () => {
|
||||||
console.error('[activity] failed to fetch activity log entries', err);
|
console.error('[activity] failed to fetch activity log entries', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [ api, paginationModel ]);
|
}, [ 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 (
|
return (
|
||||||
<Page
|
<Page
|
||||||
|
@ -152,7 +201,34 @@ const Activity = () => {
|
||||||
className='mainAnimatedPage type-interior'
|
className='mainAnimatedPage type-interior'
|
||||||
>
|
>
|
||||||
<div className='content-primary'>
|
<div className='content-primary'>
|
||||||
<h2>{globalize.translate('HeaderActivity')}</h2>
|
<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>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
|
|
@ -930,6 +930,7 @@
|
||||||
"LabelSyncPlaySettingsSpeedToSyncHelp": "Sync correction method that consist in speeding up the playback. Sync Correction must be enabled.",
|
"LabelSyncPlaySettingsSpeedToSyncHelp": "Sync correction method that consist in speeding up the playback. Sync Correction must be enabled.",
|
||||||
"LabelSyncPlaySettingsSkipToSync": "Enable SkipToSync",
|
"LabelSyncPlaySettingsSkipToSync": "Enable SkipToSync",
|
||||||
"LabelSyncPlaySettingsSkipToSyncHelp": "Sync correction method that consist in seeking to the estimated position. Sync Correction must be enabled.",
|
"LabelSyncPlaySettingsSkipToSyncHelp": "Sync correction method that consist in seeking to the estimated position. Sync Correction must be enabled.",
|
||||||
|
"LabelSystem": "System",
|
||||||
"LabelTag": "Tag",
|
"LabelTag": "Tag",
|
||||||
"LabelTagline": "Tagline",
|
"LabelTagline": "Tagline",
|
||||||
"LabelTextBackgroundColor": "Text background color",
|
"LabelTextBackgroundColor": "Text background color",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue