Skip to content

Commit 04c5f17

Browse files
authored
Merge branch 'master' into armanc/add-subtitle-sync-ui
2 parents 7c3d29d + ba9b84f commit 04c5f17

62 files changed

Lines changed: 2705 additions & 1676 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,12 @@ export default tseslint.config(
351351
leadingUnderscore: 'allowSingleOrDouble',
352352
trailingUnderscore: 'allowSingleOrDouble'
353353
},
354-
// Ignore numbers, locale strings (en-us), aria/data attributes, CSS selectors,
355-
// and api_key parameter
354+
// Ignore numbers, locale strings (en-us), aria/data attributes and CSS selectors
356355
{
357356
selector: [ 'objectLiteralProperty', 'typeProperty' ],
358357
format: null,
359358
filter: {
360-
regex: '[ &\\-]|^([0-9]+)$|^api_key$',
359+
regex: '[ &\\-]|^([0-9]+)$',
361360
match: true
362361
}
363362
}

src/apps/dashboard/AppLayout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import AppToolbar from 'components/toolbar/AppToolbar';
1212
import ServerButton from 'components/toolbar/ServerButton';
1313
import ElevationScroll from 'components/ElevationScroll';
1414
import { DRAWER_WIDTH } from 'components/ResponsiveDrawer';
15+
import { appRouter } from 'components/router/appRouter';
1516
import ThemeCss from 'components/ThemeCss';
1617
import { useApi } from 'hooks/useApi';
1718
import { useLocale } from 'hooks/useLocale';
@@ -66,6 +67,7 @@ export const Component: FC = () => {
6667
}}
6768
>
6869
<AppToolbar
70+
isBackButtonAvailable={appRouter.canGoBack()}
6971
isDrawerAvailable={!isMediumScreen && isDrawerAvailable}
7072
isDrawerOpen={isDrawerOpen}
7173
onDrawerButtonClick={onToggleDrawer}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useMemo } from 'react';
2+
import globalize from 'lib/globalize';
3+
import Widget from './Widget';
4+
import List from '@mui/material/List';
5+
import ActivityListItem from 'apps/dashboard/features/activity/components/ActivityListItem';
6+
import { useLogEntries } from 'apps/dashboard/features/activity/api/useLogEntries';
7+
import subSeconds from 'date-fns/subSeconds';
8+
import Skeleton from '@mui/material/Skeleton';
9+
import Stack from '@mui/material/Stack';
10+
11+
const ActivityLogWidget = () => {
12+
const dayBefore = useMemo(() => (
13+
subSeconds(new Date(), 24 * 60 * 60).toISOString()
14+
), []);
15+
16+
const { data: logs, isPending } = useLogEntries({
17+
startIndex: 0,
18+
limit: 7,
19+
minDate: dayBefore,
20+
hasUserId: true
21+
});
22+
23+
return (
24+
<Widget
25+
title={globalize.translate('HeaderActivity')}
26+
href='/dashboard/activity?useractivity=true'
27+
>
28+
{isPending ? (
29+
<Stack spacing={2}>
30+
<Skeleton variant='rounded' height={60} />
31+
<Skeleton variant='rounded' height={60} />
32+
<Skeleton variant='rounded' height={60} />
33+
<Skeleton variant='rounded' height={60} />
34+
</Stack>
35+
) : (
36+
<List sx={{ bgcolor: 'background.paper' }}>
37+
{logs?.Items?.map(entry => (
38+
<ActivityListItem
39+
key={entry.Id}
40+
item={entry}
41+
displayShortOverview={true}
42+
/>
43+
))}
44+
</List>
45+
)}
46+
</Widget>
47+
);
48+
};
49+
50+
export default ActivityLogWidget;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { useMemo } from 'react';
2+
import globalize from 'lib/globalize';
3+
import Widget from './Widget';
4+
import List from '@mui/material/List';
5+
import ActivityListItem from 'apps/dashboard/features/activity/components/ActivityListItem';
6+
import subSeconds from 'date-fns/subSeconds';
7+
import { useLogEntries } from 'apps/dashboard/features/activity/api/useLogEntries';
8+
9+
const AlertsLogWidget = () => {
10+
const weekBefore = useMemo(() => (
11+
subSeconds(new Date(), 7 * 24 * 60 * 60).toISOString()
12+
), []);
13+
14+
const { data: alerts, isPending } = useLogEntries({
15+
startIndex: 0,
16+
limit: 4,
17+
minDate: weekBefore,
18+
hasUserId: false
19+
});
20+
21+
if (isPending || alerts?.Items?.length === 0) return null;
22+
23+
return (
24+
<Widget
25+
title={globalize.translate('Alerts')}
26+
href='/dashboard/activity?useractivity=false'
27+
>
28+
<List sx={{ bgcolor: 'background.paper' }}>
29+
{alerts?.Items?.map(entry => (
30+
<ActivityListItem
31+
key={entry.Id}
32+
item={entry}
33+
displayShortOverview={false}
34+
/>
35+
))}
36+
</List>
37+
</Widget>
38+
);
39+
};
40+
41+
export default AlertsLogWidget;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import globalize from 'lib/globalize';
3+
import Widget from './Widget';
4+
import DeviceCard from 'apps/dashboard/features/devices/components/DeviceCard';
5+
import Stack from '@mui/material/Stack';
6+
import useLiveSessions from 'apps/dashboard/features/sessions/hooks/useLiveSessions';
7+
8+
const DevicesWidget = () => {
9+
const { data: devices } = useLiveSessions();
10+
11+
return (
12+
<Widget
13+
title={globalize.translate('HeaderDevices')}
14+
href='/dashboard/devices'
15+
>
16+
<Stack direction='row' flexWrap='wrap' gap={2}>
17+
{devices?.map(device => (
18+
<DeviceCard
19+
key={device.Id}
20+
device={device}
21+
/>
22+
))}
23+
</Stack>
24+
</Widget>
25+
);
26+
};
27+
28+
export default DevicesWidget;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { useMemo } from 'react';
2+
import globalize from 'lib/globalize';
3+
import Widget from './Widget';
4+
import type { TaskInfo } from '@jellyfin/sdk/lib/generated-client/models/task-info';
5+
import Paper from '@mui/material/Paper';
6+
import { TaskState } from '@jellyfin/sdk/lib/generated-client/models/task-state';
7+
import Typography from '@mui/material/Typography';
8+
import TaskProgress from 'apps/dashboard/features/tasks/components/TaskProgress';
9+
import Box from '@mui/material/Box';
10+
import Stack from '@mui/material/Stack';
11+
12+
type RunningTasksWidgetProps = {
13+
tasks?: TaskInfo[];
14+
};
15+
16+
const RunningTasksWidget = ({ tasks }: RunningTasksWidgetProps) => {
17+
const runningTasks = useMemo(() => {
18+
return tasks?.filter(v => v.State == TaskState.Running) || [];
19+
}, [ tasks ]);
20+
21+
if (runningTasks.length == 0) return null;
22+
23+
return (
24+
<Widget
25+
title={globalize.translate('HeaderRunningTasks')}
26+
href='/dashboard/tasks'
27+
>
28+
<Paper sx={{ padding: 2 }}>
29+
<Stack spacing={2} maxWidth={'330px'}>
30+
{runningTasks.map((task => (
31+
<Box key={task.Id}>
32+
<Typography>{task.Name}</Typography>
33+
<TaskProgress task={task} />
34+
</Box>
35+
)))}
36+
</Stack>
37+
</Paper>
38+
</Widget>
39+
);
40+
};
41+
42+
export default RunningTasksWidget;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from 'react';
2+
import globalize from 'lib/globalize';
3+
import Widget from './Widget';
4+
import Paper from '@mui/material/Paper';
5+
import Typography from '@mui/material/Typography';
6+
import Stack from '@mui/material/Stack';
7+
import Button from '@mui/material/Button';
8+
import Skeleton from '@mui/material/Skeleton';
9+
import { useSystemInfo } from 'hooks/useSystemInfo';
10+
11+
type ServerInfoWidgetProps = {
12+
onScanLibrariesClick?: () => void;
13+
onRestartClick?: () => void;
14+
onShutdownClick?: () => void;
15+
};
16+
17+
const ServerInfoWidget = ({ onScanLibrariesClick, onRestartClick, onShutdownClick }: ServerInfoWidgetProps) => {
18+
const { data: systemInfo, isPending } = useSystemInfo();
19+
20+
return (
21+
<Widget
22+
title={globalize.translate('TabServer')}
23+
href='/dashboard/settings'
24+
>
25+
<Stack spacing={2}>
26+
<Paper sx={{
27+
padding: 2
28+
}}>
29+
<Stack direction='row'>
30+
<Stack flexGrow={1} gap={1}>
31+
<Typography fontWeight='bold'>{globalize.translate('LabelServerName')}</Typography>
32+
<Typography fontWeight='bold'>{globalize.translate('LabelServerVersion')}</Typography>
33+
<Typography fontWeight='bold'>{globalize.translate('LabelWebVersion')}</Typography>
34+
<Typography fontWeight='bold'>{globalize.translate('LabelBuildVersion')}</Typography>
35+
</Stack>
36+
<Stack flexGrow={5} gap={1}>
37+
{isPending ? (
38+
<>
39+
<Skeleton />
40+
<Skeleton />
41+
<Skeleton />
42+
<Skeleton />
43+
</>
44+
) : (
45+
<>
46+
<Typography>{systemInfo?.ServerName}</Typography>
47+
<Typography>{systemInfo?.Version}</Typography>
48+
<Typography>{__PACKAGE_JSON_VERSION__}</Typography>
49+
<Typography>{__JF_BUILD_VERSION__}</Typography>
50+
</>
51+
)}
52+
</Stack>
53+
</Stack>
54+
</Paper>
55+
56+
<Stack direction='row' gap={1.5} flexWrap={'wrap'}>
57+
<Button
58+
onClick={onScanLibrariesClick}
59+
sx={{
60+
fontWeight: 'bold'
61+
}}
62+
>
63+
{globalize.translate('ButtonScanAllLibraries')}
64+
</Button>
65+
66+
<Button
67+
onClick={onRestartClick}
68+
color='error'
69+
sx={{
70+
fontWeight: 'bold'
71+
}}
72+
>
73+
{globalize.translate('Restart')}
74+
</Button>
75+
76+
<Button
77+
onClick={onShutdownClick}
78+
color='error'
79+
sx={{
80+
fontWeight: 'bold'
81+
}}
82+
>
83+
{globalize.translate('ButtonShutdown')}
84+
</Button>
85+
</Stack>
86+
</Stack>
87+
</Widget>
88+
);
89+
};
90+
91+
export default ServerInfoWidget;

src/apps/dashboard/components/widgets/ServerPathWidget.tsx

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
1-
import ChevronRight from '@mui/icons-material/ChevronRight';
2-
import Button from '@mui/material/Button';
31
import List from '@mui/material/List';
4-
import Typography from '@mui/material/Typography';
52
import React from 'react';
6-
7-
import { useSystemStorage } from 'apps/dashboard/features/storage/api/useSystemStorage';
83
import StorageListItem from 'apps/dashboard/features/storage/components/StorageListItem';
94
import globalize from 'lib/globalize';
5+
import Widget from './Widget';
6+
import { useSystemStorage } from 'apps/dashboard/features/storage/api/useSystemStorage';
107

118
const ServerPathWidget = () => {
129
const { data: systemStorage } = useSystemStorage();
1310

1411
return (
15-
<>
16-
<Button
17-
variant='text'
18-
color='inherit'
19-
endIcon={<ChevronRight />}
20-
sx={{
21-
marginTop: 1,
22-
marginBottom: 1
23-
}}
24-
// NOTE: We should use a react-router Link component, but components rendered in legacy views lack the
25-
// routing context
26-
href='#/dashboard/settings'
27-
>
28-
<Typography variant='h3' component='span'>
29-
{globalize.translate('HeaderPaths')}
30-
</Typography>
31-
</Button>
32-
12+
<Widget
13+
title={globalize.translate('HeaderPaths')}
14+
href='/dashboard/settings'
15+
>
3316
<List sx={{ bgcolor: 'background.paper' }}>
3417
<StorageListItem
3518
label={globalize.translate('LabelCache')}
@@ -60,7 +43,7 @@ const ServerPathWidget = () => {
6043
folder={systemStorage?.WebFolder}
6144
/>
6245
</List>
63-
</>
46+
</Widget>
6447
);
6548
};
6649

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Box from '@mui/material/Box';
2+
import Button from '@mui/material/Button';
3+
import Typography from '@mui/material/Typography';
4+
import ChevronRight from '@mui/icons-material/ChevronRight';
5+
import React from 'react';
6+
import { Link as RouterLink } from 'react-router-dom';
7+
8+
type WidgetProps = {
9+
title: string;
10+
href: string;
11+
children: React.ReactNode;
12+
};
13+
14+
const Widget = ({ title, href, children }: WidgetProps) => {
15+
return (
16+
<Box>
17+
<Button
18+
component={RouterLink}
19+
to={href}
20+
variant='text'
21+
color='inherit'
22+
endIcon={<ChevronRight />}
23+
sx={{
24+
marginTop: 1,
25+
marginBottom: 1
26+
}}
27+
>
28+
<Typography variant='h3' component='span'>
29+
{title}
30+
</Typography>
31+
</Button>
32+
33+
{children}
34+
</Box>
35+
);
36+
};
37+
38+
export default Widget;

0 commit comments

Comments
 (0)