From bbec426232ae50451119c61bf203f4c0016c6b5e Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:55:15 +0300 Subject: [PATCH 01/18] Migrate logs to React --- src/apps/dashboard/routes/_asyncRoutes.ts | 1 + src/apps/dashboard/routes/_legacyRoutes.ts | 6 - src/apps/dashboard/routes/logs.tsx | 142 +++++++++++++++++++++ src/components/dashboard/logs/LogItem.tsx | 40 ++++++ src/controllers/dashboard/logs.html | 33 ----- src/controllers/dashboard/logs.js | 63 --------- 6 files changed, 183 insertions(+), 102 deletions(-) create mode 100644 src/apps/dashboard/routes/logs.tsx create mode 100644 src/components/dashboard/logs/LogItem.tsx delete mode 100644 src/controllers/dashboard/logs.html delete mode 100644 src/controllers/dashboard/logs.js diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index c2e1e4a8d2..0e4d250b8c 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -5,6 +5,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'branding', type: AsyncRouteType.Dashboard }, { path: 'playback/trickplay', type: AsyncRouteType.Dashboard }, { path: 'plugins/:pluginId', page: 'plugins/plugin', type: AsyncRouteType.Dashboard }, + { path: 'logs', type: AsyncRouteType.Dashboard }, { path: 'users', type: AsyncRouteType.Dashboard }, { path: 'users/access', type: AsyncRouteType.Dashboard }, { path: 'users/add', type: AsyncRouteType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index ae82c7e5f7..64911dc20a 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -49,12 +49,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'dashboard/encodingsettings', view: 'dashboard/encodingsettings.html' } - }, { - path: 'logs', - pageProps: { - controller: 'dashboard/logs', - view: 'dashboard/logs.html' - } }, { path: 'libraries/metadata', pageProps: { diff --git a/src/apps/dashboard/routes/logs.tsx b/src/apps/dashboard/routes/logs.tsx new file mode 100644 index 0000000000..76b1a15046 --- /dev/null +++ b/src/apps/dashboard/routes/logs.tsx @@ -0,0 +1,142 @@ +import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; +import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; +import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; +import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; +import LogItem from 'components/dashboard/logs/LogItem'; +import Loading from 'components/loading/LoadingComponent'; +import Page from 'components/Page'; +import ButtonElement from 'elements/ButtonElement'; +import CheckBoxElement from 'elements/CheckBoxElement'; +import InputElement from 'elements/InputElement'; +import SectionTitleContainer from 'elements/SectionTitleContainer'; +import { useApi } from 'hooks/useApi'; +import globalize from 'lib/globalize'; +import toast from 'components/toast/toast'; + +const Logs = () => { + const { api } = useApi(); + const [ logs, setLogs ] = useState([]); + const [ loading, setLoading ] = useState(false); + const element = useRef(null); + + const loadLogs = useCallback(() => { + if (!api) return; + + return getSystemApi(api) + .getServerLogs() + .then(({ data }) => { + setLogs(data); + }); + }, [api]); + + const onSubmit = useCallback((e: FormEvent) => { + e.preventDefault(); + if (!api) return; + + const page = element.current; + + if (!page) return; + + getConfigurationApi(api) + .getConfiguration() + .then(({ data: config }) => { + config.EnableSlowResponseWarning = (page.querySelector('.chkSlowResponseWarning') as HTMLInputElement).checked; + config.SlowResponseThresholdMs = parseInt((page.querySelector('#txtSlowResponseWarning') as HTMLInputElement).value, 10); + getConfigurationApi(api) + .updateConfiguration({ serverConfiguration: config }) + .then(() => toast(globalize.translate('SettingsSaved'))) + .catch(err => { + console.error('[logs] failed to update configuration data', err); + }); + }) + .catch(err => { + console.error('[logs] failed to get configuration data', err); + }); + }, [api]); + + useEffect(() => { + if (!api) return; + + loadLogs()?.then(() => { + setLoading(false); + }).catch(err => { + console.error('[logs] An error occurred while fetching logs', err); + }); + + const page = element.current; + + if (!page || loading) return; + + getConfigurationApi(api) + .getConfiguration() + .then(({ data: config }) => { + if (config.EnableSlowResponseWarning) { + (page.querySelector('.chkSlowResponseWarning') as HTMLInputElement).checked = config.EnableSlowResponseWarning; + } + if (config.SlowResponseThresholdMs != null) { + (page.querySelector('#txtSlowResponseWarning') as HTMLInputElement).value = String(config.SlowResponseThresholdMs); + } + }) + .catch(err => { + console.error('[logs] An error occurred while fetching system config', err); + }); + }, [loading, api, loadLogs]); + + if (loading) { + return ; + } + + return ( + +
+
+
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ {logs.map(log => { + return ; + })} +
+
+
+
+ ); +}; + +export default Logs; diff --git a/src/components/dashboard/logs/LogItem.tsx b/src/components/dashboard/logs/LogItem.tsx new file mode 100644 index 0000000000..be4c0193cc --- /dev/null +++ b/src/components/dashboard/logs/LogItem.tsx @@ -0,0 +1,40 @@ +import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; +import LinkButton from 'elements/emby-button/LinkButton'; +import { useApi } from 'hooks/useApi'; +import React, { FunctionComponent } from 'react'; +import datetime from 'scripts/datetime'; + +type LogItemProps = { + logFile: LogFile; +}; + +const LogItem: FunctionComponent = ({ logFile }: LogItemProps) => { + const { api } = useApi(); + + const getLogFileUrl = () => { + if (!api) return; + + let url = api.basePath + '/System/Logs/Log'; + + url += '?name=' + encodeURIComponent(String(logFile.Name)); + url += '&api_key=' + encodeURIComponent(api.accessToken); + + return url; + }; + + const getDate = () => { + const date = datetime.parseISO8601Date(logFile.DateModified, true); + return datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); + }; + + return ( + +
+

{logFile.Name}

+
{getDate()}
+
+
+ ); +}; + +export default LogItem; diff --git a/src/controllers/dashboard/logs.html b/src/controllers/dashboard/logs.html deleted file mode 100644 index a23d7750f4..0000000000 --- a/src/controllers/dashboard/logs.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
-
-
-
-
-

${TabLogs}

-
-
- -
-
- -
-
- -
-
-
-
- -
-
-
-
-
-
-
diff --git a/src/controllers/dashboard/logs.js b/src/controllers/dashboard/logs.js deleted file mode 100644 index 110d4be815..0000000000 --- a/src/controllers/dashboard/logs.js +++ /dev/null @@ -1,63 +0,0 @@ -import datetime from '../../scripts/datetime'; -import loading from '../../components/loading/loading'; -import globalize from '../../lib/globalize'; -import '../../elements/emby-button/emby-button'; -import '../../components/listview/listview.scss'; -import '../../styles/flexstyles.scss'; -import Dashboard from '../../utils/dashboard'; -import alert from '../../components/alert'; - -function onSubmit(event) { - event.preventDefault(); - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.EnableSlowResponseWarning = form.querySelector('#chkSlowResponseWarning').checked; - config.SlowResponseThresholdMs = form.querySelector('#txtSlowResponseWarning').value; - ApiClient.updateServerConfiguration(config).then(function() { - Dashboard.processServerConfigurationUpdateResult(); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - return false; -} - -export default function(view) { - view.querySelector('.logsForm').addEventListener('submit', onSubmit); - view.addEventListener('viewbeforeshow', function() { - loading.show(); - const apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { - let html = ''; - html += '
'; - html += logs.map(function(log) { - let logUrl = apiClient.getUrl('System/Logs/Log', { - name: log.Name - }); - logUrl += '&api_key=' + apiClient.accessToken(); - let logHtml = ''; - logHtml += ''; - logHtml += '
'; - logHtml += "

" + log.Name + '

'; - const date = datetime.parseISO8601Date(log.DateModified, true); - let text = datetime.toLocaleDateString(date); - text += ' ' + datetime.getDisplayTime(date); - logHtml += '
' + text + '
'; - logHtml += '
'; - logHtml += '
'; - return logHtml; - }).join(''); - html += '
'; - view.querySelector('.serverLogs').innerHTML = html; - }); - - apiClient.getServerConfiguration().then(function (config) { - view.querySelector('#chkSlowResponseWarning').checked = config.EnableSlowResponseWarning; - view.querySelector('#txtSlowResponseWarning').value = config.SlowResponseThresholdMs; - }); - - loading.hide(); - }); -} From 2e4848ade950ae529bce3de1b40975728d37583e Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:00:24 +0300 Subject: [PATCH 02/18] Use mui components --- src/apps/dashboard/routes/logs.tsx | 122 +++++++++++++++-------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/src/apps/dashboard/routes/logs.tsx b/src/apps/dashboard/routes/logs.tsx index 76b1a15046..3db25aa354 100644 --- a/src/apps/dashboard/routes/logs.tsx +++ b/src/apps/dashboard/routes/logs.tsx @@ -1,23 +1,31 @@ -import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; +import React, { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react'; import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; import LogItem from 'components/dashboard/logs/LogItem'; import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; -import ButtonElement from 'elements/ButtonElement'; -import CheckBoxElement from 'elements/CheckBoxElement'; -import InputElement from 'elements/InputElement'; -import SectionTitleContainer from 'elements/SectionTitleContainer'; import { useApi } from 'hooks/useApi'; import globalize from 'lib/globalize'; -import toast from 'components/toast/toast'; +import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typography } from '@mui/material'; +import { Form } from 'react-router-dom'; const Logs = () => { const { api } = useApi(); const [ logs, setLogs ] = useState([]); - const [ loading, setLoading ] = useState(false); - const element = useRef(null); + const [ logsLoading, setLogsLoading ] = useState(true); + const [ configLoading, setConfigLoading ] = useState(true); + const [ logWarningMessageChecked, setLogWarningMessageChecked ] = useState(false); + const [ slowResponseTime, setSlowResponseTime ] = useState(''); + const [ submitted, setSubmitted ] = useState(false); + + const setLogWarningMessage = useCallback((_: ChangeEvent, checked: boolean) => { + setLogWarningMessageChecked(checked); + }, []); + + const onResponseTimeChange = useCallback((event: ChangeEvent) => { + setSlowResponseTime(event.target.value); + }, []); const loadLogs = useCallback(() => { if (!api) return; @@ -33,18 +41,14 @@ const Logs = () => { e.preventDefault(); if (!api) return; - const page = element.current; - - if (!page) return; - getConfigurationApi(api) .getConfiguration() .then(({ data: config }) => { - config.EnableSlowResponseWarning = (page.querySelector('.chkSlowResponseWarning') as HTMLInputElement).checked; - config.SlowResponseThresholdMs = parseInt((page.querySelector('#txtSlowResponseWarning') as HTMLInputElement).value, 10); + config.EnableSlowResponseWarning = logWarningMessageChecked; + config.SlowResponseThresholdMs = parseInt(slowResponseTime, 10); getConfigurationApi(api) .updateConfiguration({ serverConfiguration: config }) - .then(() => toast(globalize.translate('SettingsSaved'))) + .then(() => setSubmitted(true)) .catch(err => { console.error('[logs] failed to update configuration data', err); }); @@ -52,37 +56,34 @@ const Logs = () => { .catch(err => { console.error('[logs] failed to get configuration data', err); }); - }, [api]); + }, [api, logWarningMessageChecked, slowResponseTime]); useEffect(() => { if (!api) return; loadLogs()?.then(() => { - setLoading(false); + setLogsLoading(false); }).catch(err => { console.error('[logs] An error occurred while fetching logs', err); }); - const page = element.current; - - if (!page || loading) return; - getConfigurationApi(api) .getConfiguration() .then(({ data: config }) => { if (config.EnableSlowResponseWarning) { - (page.querySelector('.chkSlowResponseWarning') as HTMLInputElement).checked = config.EnableSlowResponseWarning; + setLogWarningMessageChecked(config.EnableSlowResponseWarning); } if (config.SlowResponseThresholdMs != null) { - (page.querySelector('#txtSlowResponseWarning') as HTMLInputElement).value = String(config.SlowResponseThresholdMs); + setSlowResponseTime(String(config.SlowResponseThresholdMs)); } + setConfigLoading(false); }) .catch(err => { console.error('[logs] An error occurred while fetching system config', err); }); - }, [loading, api, loadLogs]); + }, [logsLoading, configLoading, api, loadLogs]); - if (loading) { + if (logsLoading || configLoading) { return ; } @@ -92,38 +93,45 @@ const Logs = () => { title={globalize.translate('TabLogs')} className='mainAnimatedPage type-interior' > -
-
-
- -
+ + + + + {globalize.translate('TabLogs')} + -
-
- -
-
- -
-
-
- -
-
- + {submitted && ( + + {globalize.translate('SettingsSaved')} + + )} + + + } + label={globalize.translate('LabelSlowResponseEnabled')} + /> + + + + +
+
{logs.map(log => { @@ -134,7 +142,7 @@ const Logs = () => { })}
-
+ ); }; From 0e54b11c61e281f3dbc9ddcb25d1e5ff2b8a9d28 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:31:03 +0300 Subject: [PATCH 03/18] Move submission to action --- .../features/logs/api/useLogEntries.ts | 0 .../features/logs/api/useLogOptions.ts | 0 .../routes/{logs.tsx => logs/index.tsx} | 65 +++++++++++-------- 3 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 src/apps/dashboard/features/logs/api/useLogEntries.ts create mode 100644 src/apps/dashboard/features/logs/api/useLogOptions.ts rename src/apps/dashboard/routes/{logs.tsx => logs/index.tsx} (75%) diff --git a/src/apps/dashboard/features/logs/api/useLogEntries.ts b/src/apps/dashboard/features/logs/api/useLogEntries.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/apps/dashboard/features/logs/api/useLogOptions.ts b/src/apps/dashboard/features/logs/api/useLogOptions.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/apps/dashboard/routes/logs.tsx b/src/apps/dashboard/routes/logs/index.tsx similarity index 75% rename from src/apps/dashboard/routes/logs.tsx rename to src/apps/dashboard/routes/logs/index.tsx index 3db25aa354..8699a31426 100644 --- a/src/apps/dashboard/routes/logs.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react'; +import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; @@ -8,16 +8,44 @@ import Page from 'components/Page'; import { useApi } from 'hooks/useApi'; import globalize from 'lib/globalize'; import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typography } from '@mui/material'; -import { Form } from 'react-router-dom'; +import { type ActionFunctionArgs, type LoaderFunctionArgs, Form, useActionData } from 'react-router-dom'; +import ServerConnections from 'components/ServerConnections'; -const Logs = () => { +interface ActionData { + isSaved: boolean; +} + +export const action = async ({ request }: ActionFunctionArgs) => { + const api = ServerConnections.getCurrentApi(); + if (!api) throw new Error('No Api instance available'); + + const formData = await request.formData(); + const { data: config } = await getConfigurationApi(api).getConfiguration(); + + config.EnableSlowResponseWarning = formData.get('EnableWarningMessage') === 'on'; + + const responseTime = formData.get('SlowResponseTime'); + if (responseTime) { + config.SlowResponseThresholdMs = parseInt(responseTime.toString(), 10); + } + + await getConfigurationApi(api) + .updateConfiguration({ serverConfiguration: config }); + + return { + isSaved: true + }; +}; + +export const Logs = () => { + const actionData = useActionData() as ActionData | undefined; const { api } = useApi(); const [ logs, setLogs ] = useState([]); const [ logsLoading, setLogsLoading ] = useState(true); const [ configLoading, setConfigLoading ] = useState(true); const [ logWarningMessageChecked, setLogWarningMessageChecked ] = useState(false); const [ slowResponseTime, setSlowResponseTime ] = useState(''); - const [ submitted, setSubmitted ] = useState(false); + const [ isSubmitting, setIsSubmitting ] = useState(false); const setLogWarningMessage = useCallback((_: ChangeEvent, checked: boolean) => { setLogWarningMessageChecked(checked); @@ -37,26 +65,9 @@ const Logs = () => { }); }, [api]); - const onSubmit = useCallback((e: FormEvent) => { - e.preventDefault(); - if (!api) return; - - getConfigurationApi(api) - .getConfiguration() - .then(({ data: config }) => { - config.EnableSlowResponseWarning = logWarningMessageChecked; - config.SlowResponseThresholdMs = parseInt(slowResponseTime, 10); - getConfigurationApi(api) - .updateConfiguration({ serverConfiguration: config }) - .then(() => setSubmitted(true)) - .catch(err => { - console.error('[logs] failed to update configuration data', err); - }); - }) - .catch(err => { - console.error('[logs] failed to get configuration data', err); - }); - }, [api, logWarningMessageChecked, slowResponseTime]); + const onSubmit = useCallback(() => { + setIsSubmitting(true); + }, []); useEffect(() => { if (!api) return; @@ -94,13 +105,13 @@ const Logs = () => { className='mainAnimatedPage type-interior' > -
+ {globalize.translate('TabLogs')} - {submitted && ( + {isSubmitting && actionData?.isSaved && ( {globalize.translate('SettingsSaved')} @@ -111,6 +122,7 @@ const Logs = () => { } label={globalize.translate('LabelSlowResponseEnabled')} @@ -119,6 +131,7 @@ const Logs = () => { Date: Tue, 17 Dec 2024 21:35:45 +0300 Subject: [PATCH 04/18] Move data loading to separate hooks --- .../features/activity/api/useLogEntries.ts | 1 - .../features/logs/api/useLogEntries.ts | 25 ++++++ .../features/logs/api/useLogOptions.ts | 25 ++++++ src/apps/dashboard/routes/logs/index.tsx | 85 +++++++------------ 4 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/apps/dashboard/features/activity/api/useLogEntries.ts b/src/apps/dashboard/features/activity/api/useLogEntries.ts index b0f1be5233..443fd3a725 100644 --- a/src/apps/dashboard/features/activity/api/useLogEntries.ts +++ b/src/apps/dashboard/features/activity/api/useLogEntries.ts @@ -3,7 +3,6 @@ import type { AxiosRequestConfig } from 'axios'; import type { Api } from '@jellyfin/sdk'; import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api'; import { useQuery } from '@tanstack/react-query'; - import { useApi } from 'hooks/useApi'; const fetchLogEntries = async ( diff --git a/src/apps/dashboard/features/logs/api/useLogEntries.ts b/src/apps/dashboard/features/logs/api/useLogEntries.ts index e69de29bb2..2395a10e66 100644 --- a/src/apps/dashboard/features/logs/api/useLogEntries.ts +++ b/src/apps/dashboard/features/logs/api/useLogEntries.ts @@ -0,0 +1,25 @@ +import { Api } from '@jellyfin/sdk'; +import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; +import { useQuery } from '@tanstack/react-query'; +import { useApi } from 'hooks/useApi'; + +const fetchLogEntries = async (api?: Api) => { + if (!api) { + console.error('[useLogEntries] No API instance available'); + return; + } + + const response = await getSystemApi(api).getServerLogs(); + + return response.data; +}; + +export const useLogEntries = () => { + const { api } = useApi(); + + return useQuery({ + queryKey: [ 'LogEntries' ], + queryFn: () => fetchLogEntries(api), + enabled: !!api + }); +}; diff --git a/src/apps/dashboard/features/logs/api/useLogOptions.ts b/src/apps/dashboard/features/logs/api/useLogOptions.ts index e69de29bb2..3cd9b68594 100644 --- a/src/apps/dashboard/features/logs/api/useLogOptions.ts +++ b/src/apps/dashboard/features/logs/api/useLogOptions.ts @@ -0,0 +1,25 @@ +import { Api } from '@jellyfin/sdk'; +import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; +import { useQuery } from '@tanstack/react-query'; +import { useApi } from 'hooks/useApi'; + +export const fetchLogOptions = async (api?: Api) => { + if (!api) { + console.error('[useLogOptions] No API instance available'); + return; + } + + const response = await getConfigurationApi(api).getConfiguration(); + + return response.data; +}; + +export const useLogOptions = () => { + const { api } = useApi(); + + return useQuery({ + queryKey: ['LogOptions'], + queryFn: () => fetchLogOptions(api), + enabled: !!api + }); +}; diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 8699a31426..2337c5e83f 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -1,15 +1,15 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; -import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; -import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; import LogItem from 'components/dashboard/logs/LogItem'; import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; -import { useApi } from 'hooks/useApi'; import globalize from 'lib/globalize'; import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typography } from '@mui/material'; -import { type ActionFunctionArgs, type LoaderFunctionArgs, Form, useActionData } from 'react-router-dom'; +import { type ActionFunctionArgs, Form, useActionData } from 'react-router-dom'; import ServerConnections from 'components/ServerConnections'; +import { useLogEntries } from 'apps/dashboard/features/logs/api/useLogEntries'; +import { useLogOptions } from 'apps/dashboard/features/logs/api/useLogOptions'; +import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; interface ActionData { isSaved: boolean; @@ -37,64 +37,41 @@ export const action = async ({ request }: ActionFunctionArgs) => { }; }; -export const Logs = () => { +const Logs = () => { const actionData = useActionData() as ActionData | undefined; - const { api } = useApi(); - const [ logs, setLogs ] = useState([]); - const [ logsLoading, setLogsLoading ] = useState(true); - const [ configLoading, setConfigLoading ] = useState(true); - const [ logWarningMessageChecked, setLogWarningMessageChecked ] = useState(false); - const [ slowResponseTime, setSlowResponseTime ] = useState(''); const [ isSubmitting, setIsSubmitting ] = useState(false); + const { isPending: isLogEntriesPending, data: logs } = useLogEntries(); + const { isPending: isLogOptionsPending, data: defaultLogOptions } = useLogOptions(); + const [ loading, setLoading ] = useState(true); + const [ logOptions, setLogOptions ] = useState( {} ); + + useEffect(() => { + if (!isLogOptionsPending && defaultLogOptions) { + setLogOptions(defaultLogOptions); + setLoading(false); + } + }, [isLogOptionsPending, defaultLogOptions]); + const setLogWarningMessage = useCallback((_: ChangeEvent, checked: boolean) => { - setLogWarningMessageChecked(checked); - }, []); + setLogOptions({ + ...logOptions, + EnableSlowResponseWarning: checked + }); + }, [logOptions]); const onResponseTimeChange = useCallback((event: ChangeEvent) => { - setSlowResponseTime(event.target.value); - }, []); - - const loadLogs = useCallback(() => { - if (!api) return; - - return getSystemApi(api) - .getServerLogs() - .then(({ data }) => { - setLogs(data); - }); - }, [api]); + setLogOptions({ + ...logOptions, + SlowResponseThresholdMs: parseInt(event.target.value, 10) + }); + }, [logOptions]); const onSubmit = useCallback(() => { setIsSubmitting(true); }, []); - useEffect(() => { - if (!api) return; - - loadLogs()?.then(() => { - setLogsLoading(false); - }).catch(err => { - console.error('[logs] An error occurred while fetching logs', err); - }); - - getConfigurationApi(api) - .getConfiguration() - .then(({ data: config }) => { - if (config.EnableSlowResponseWarning) { - setLogWarningMessageChecked(config.EnableSlowResponseWarning); - } - if (config.SlowResponseThresholdMs != null) { - setSlowResponseTime(String(config.SlowResponseThresholdMs)); - } - setConfigLoading(false); - }) - .catch(err => { - console.error('[logs] An error occurred while fetching system config', err); - }); - }, [logsLoading, configLoading, api, loadLogs]); - - if (logsLoading || configLoading) { + if (isLogEntriesPending || isLogOptionsPending || loading) { return ; } @@ -120,7 +97,7 @@ export const Logs = () => { @@ -133,7 +110,7 @@ export const Logs = () => { type='number' name={'SlowResponseTime'} label={globalize.translate('LabelSlowResponseTime')} - value={slowResponseTime} + value={logOptions?.SlowResponseThresholdMs} onChange={onResponseTimeChange} /> @@ -147,7 +124,7 @@ export const Logs = () => {
- {logs.map(log => { + {logs?.map(log => { return Date: Tue, 17 Dec 2024 21:37:40 +0300 Subject: [PATCH 05/18] Move LogItem component to be under features --- src/apps/dashboard/features/activity/api/useLogEntries.ts | 1 + .../dashboard/features/logs/components}/LogItem.tsx | 0 src/apps/dashboard/routes/logs/index.tsx | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename src/{components/dashboard/logs => apps/dashboard/features/logs/components}/LogItem.tsx (100%) diff --git a/src/apps/dashboard/features/activity/api/useLogEntries.ts b/src/apps/dashboard/features/activity/api/useLogEntries.ts index 443fd3a725..b0f1be5233 100644 --- a/src/apps/dashboard/features/activity/api/useLogEntries.ts +++ b/src/apps/dashboard/features/activity/api/useLogEntries.ts @@ -3,6 +3,7 @@ import type { AxiosRequestConfig } from 'axios'; import type { Api } from '@jellyfin/sdk'; import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api'; import { useQuery } from '@tanstack/react-query'; + import { useApi } from 'hooks/useApi'; const fetchLogEntries = async ( diff --git a/src/components/dashboard/logs/LogItem.tsx b/src/apps/dashboard/features/logs/components/LogItem.tsx similarity index 100% rename from src/components/dashboard/logs/LogItem.tsx rename to src/apps/dashboard/features/logs/components/LogItem.tsx diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 2337c5e83f..072c5efc20 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; -import LogItem from 'components/dashboard/logs/LogItem'; +import LogItem from 'apps/dashboard/features/logs/components/LogItem'; import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; import globalize from 'lib/globalize'; From a9a287d9fd3bdbdcf2f012edca465d129ddc5e40 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:38:03 +0300 Subject: [PATCH 06/18] Move ActionData to separate file --- src/apps/dashboard/routes/branding/index.tsx | 5 +---- src/apps/dashboard/routes/logs/index.tsx | 5 +---- src/types/actionData.ts | 3 +++ 3 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 src/types/actionData.ts diff --git a/src/apps/dashboard/routes/branding/index.tsx b/src/apps/dashboard/routes/branding/index.tsx index 9958f13803..7859610a5c 100644 --- a/src/apps/dashboard/routes/branding/index.tsx +++ b/src/apps/dashboard/routes/branding/index.tsx @@ -17,10 +17,7 @@ import Page from 'components/Page'; import ServerConnections from 'components/ServerConnections'; import globalize from 'lib/globalize'; import { queryClient } from 'utils/query/queryClient'; - -interface ActionData { - isSaved: boolean -} +import { ActionData } from 'types/actionData'; const BRANDING_CONFIG_KEY = 'branding'; const BrandingOption = { diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 072c5efc20..c01273261e 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -10,10 +10,7 @@ import ServerConnections from 'components/ServerConnections'; import { useLogEntries } from 'apps/dashboard/features/logs/api/useLogEntries'; import { useLogOptions } from 'apps/dashboard/features/logs/api/useLogOptions'; import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; - -interface ActionData { - isSaved: boolean; -} +import { ActionData } from 'types/actionData'; export const action = async ({ request }: ActionFunctionArgs) => { const api = ServerConnections.getCurrentApi(); diff --git a/src/types/actionData.ts b/src/types/actionData.ts new file mode 100644 index 0000000000..5524d64f54 --- /dev/null +++ b/src/types/actionData.ts @@ -0,0 +1,3 @@ +export interface ActionData { + isSaved: boolean; +} From 0efb4de8569e310c6fd34f5ae7bcea7cdb248aa4 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:03:09 +0300 Subject: [PATCH 07/18] Add check for warning message --- src/apps/dashboard/routes/logs/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index c01273261e..a348bcae62 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -19,7 +19,10 @@ export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const { data: config } = await getConfigurationApi(api).getConfiguration(); - config.EnableSlowResponseWarning = formData.get('EnableWarningMessage') === 'on'; + const enableWarningMessage = formData.get('EnableWarningMessage'); + if (enableWarningMessage) { + config.EnableSlowResponseWarning = formData.get('EnableWarningMessage') === 'on'; + } const responseTime = formData.get('SlowResponseTime'); if (responseTime) { From acea8d34b9d026deb16e19073e5d1ac0d7a5b15b Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:55:05 +0300 Subject: [PATCH 08/18] Move activity.tsx to activity/index.tsx --- src/apps/dashboard/routes/{activity.tsx => activity/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/apps/dashboard/routes/{activity.tsx => activity/index.tsx} (100%) diff --git a/src/apps/dashboard/routes/activity.tsx b/src/apps/dashboard/routes/activity/index.tsx similarity index 100% rename from src/apps/dashboard/routes/activity.tsx rename to src/apps/dashboard/routes/activity/index.tsx From 9bb2541ad87a743bee66eed48ff579e1b9e3000f Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:53:35 +0300 Subject: [PATCH 09/18] Give activity logs a unique key and fix typo --- src/apps/dashboard/features/activity/api/useLogEntries.ts | 4 ++-- src/apps/dashboard/routes/activity/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apps/dashboard/features/activity/api/useLogEntries.ts b/src/apps/dashboard/features/activity/api/useLogEntries.ts index b0f1be5233..ba956d5171 100644 --- a/src/apps/dashboard/features/activity/api/useLogEntries.ts +++ b/src/apps/dashboard/features/activity/api/useLogEntries.ts @@ -23,12 +23,12 @@ const fetchLogEntries = async ( return response.data; }; -export const useLogEntires = ( +export const useLogEntries = ( requestParams: ActivityLogApiGetLogEntriesRequest ) => { const { api } = useApi(); return useQuery({ - queryKey: ['LogEntries', requestParams], + queryKey: ['ActivityLogEntries', requestParams], queryFn: ({ signal }) => fetchLogEntries(api, requestParams, { signal }), enabled: !!api diff --git a/src/apps/dashboard/routes/activity/index.tsx b/src/apps/dashboard/routes/activity/index.tsx index 666d08441c..5b0e328777 100644 --- a/src/apps/dashboard/routes/activity/index.tsx +++ b/src/apps/dashboard/routes/activity/index.tsx @@ -9,7 +9,7 @@ import Typography from '@mui/material/Typography'; import { type MRT_ColumnDef, MaterialReactTable, useMaterialReactTable } from 'material-react-table'; import { useSearchParams } from 'react-router-dom'; -import { useLogEntires } from 'apps/dashboard/features/activity/api/useLogEntries'; +import { useLogEntries } from 'apps/dashboard/features/activity/api/useLogEntries'; import ActionsCell from 'apps/dashboard/features/activity/components/ActionsCell'; import LogLevelCell from 'apps/dashboard/features/activity/components/LogLevelCell'; import OverviewCell from 'apps/dashboard/features/activity/components/OverviewCell'; @@ -87,7 +87,7 @@ const Activity = () => { hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined }), [activityView, pagination.pageIndex, pagination.pageSize]); - const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntires(activityParams); + const { data: logEntries, isLoading: isLogEntriesLoading } = useLogEntries(activityParams); const isLoading = isUsersLoading || isLogEntriesLoading; From 6a8fdc7e81501f79ab6bd8374ec8d1d6426b0410 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:52:05 +0300 Subject: [PATCH 10/18] Add abort signal to api hooks --- src/apps/dashboard/features/logs/api/useLogEntries.ts | 7 ++++--- src/apps/dashboard/features/logs/api/useLogOptions.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/apps/dashboard/features/logs/api/useLogEntries.ts b/src/apps/dashboard/features/logs/api/useLogEntries.ts index 2395a10e66..5526645bd5 100644 --- a/src/apps/dashboard/features/logs/api/useLogEntries.ts +++ b/src/apps/dashboard/features/logs/api/useLogEntries.ts @@ -2,14 +2,15 @@ import { Api } from '@jellyfin/sdk'; import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api'; import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; +import type { AxiosRequestConfig } from 'axios'; -const fetchLogEntries = async (api?: Api) => { +const fetchLogEntries = async (api?: Api, options?: AxiosRequestConfig) => { if (!api) { console.error('[useLogEntries] No API instance available'); return; } - const response = await getSystemApi(api).getServerLogs(); + const response = await getSystemApi(api).getServerLogs(options); return response.data; }; @@ -19,7 +20,7 @@ export const useLogEntries = () => { return useQuery({ queryKey: [ 'LogEntries' ], - queryFn: () => fetchLogEntries(api), + queryFn: ({ signal }) => fetchLogEntries(api, { signal }), enabled: !!api }); }; diff --git a/src/apps/dashboard/features/logs/api/useLogOptions.ts b/src/apps/dashboard/features/logs/api/useLogOptions.ts index 3cd9b68594..a8c4bf07e5 100644 --- a/src/apps/dashboard/features/logs/api/useLogOptions.ts +++ b/src/apps/dashboard/features/logs/api/useLogOptions.ts @@ -2,14 +2,15 @@ import { Api } from '@jellyfin/sdk'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; +import type { AxiosRequestConfig } from 'axios'; -export const fetchLogOptions = async (api?: Api) => { +export const fetchLogOptions = async (api?: Api, options?: AxiosRequestConfig) => { if (!api) { console.error('[useLogOptions] No API instance available'); return; } - const response = await getConfigurationApi(api).getConfiguration(); + const response = await getConfigurationApi(api).getConfiguration(options); return response.data; }; @@ -19,7 +20,7 @@ export const useLogOptions = () => { return useQuery({ queryKey: ['LogOptions'], - queryFn: () => fetchLogOptions(api), + queryFn: ({ signal }) => fetchLogOptions(api, { signal }), enabled: !!api }); }; From e08de5175dbfeb0039edc3777325272f339cfd74 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:27:38 +0300 Subject: [PATCH 11/18] Move to alphabetical order --- src/apps/dashboard/routes/_asyncRoutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 0e4d250b8c..bd7e264318 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -3,9 +3,9 @@ import { AsyncRouteType, type AsyncRoute } from 'components/router/AsyncRoute'; export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'activity', type: AsyncRouteType.Dashboard }, { path: 'branding', type: AsyncRouteType.Dashboard }, + { path: 'logs', type: AsyncRouteType.Dashboard }, { path: 'playback/trickplay', type: AsyncRouteType.Dashboard }, { path: 'plugins/:pluginId', page: 'plugins/plugin', type: AsyncRouteType.Dashboard }, - { path: 'logs', type: AsyncRouteType.Dashboard }, { path: 'users', type: AsyncRouteType.Dashboard }, { path: 'users/access', type: AsyncRouteType.Dashboard }, { path: 'users/add', type: AsyncRouteType.Dashboard }, From da31c9856cea9e6ae912223582f96e97cc8dfe32 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:07:56 +0300 Subject: [PATCH 12/18] Rename to useServerLogs --- .../logs/api/{useLogEntries.ts => useServerLogs.ts} | 6 +++--- src/apps/dashboard/routes/logs/index.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/apps/dashboard/features/logs/api/{useLogEntries.ts => useServerLogs.ts} (78%) diff --git a/src/apps/dashboard/features/logs/api/useLogEntries.ts b/src/apps/dashboard/features/logs/api/useServerLogs.ts similarity index 78% rename from src/apps/dashboard/features/logs/api/useLogEntries.ts rename to src/apps/dashboard/features/logs/api/useServerLogs.ts index 5526645bd5..b0e2001523 100644 --- a/src/apps/dashboard/features/logs/api/useLogEntries.ts +++ b/src/apps/dashboard/features/logs/api/useServerLogs.ts @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import type { AxiosRequestConfig } from 'axios'; -const fetchLogEntries = async (api?: Api, options?: AxiosRequestConfig) => { +const fetchServerLogs = async (api?: Api, options?: AxiosRequestConfig) => { if (!api) { console.error('[useLogEntries] No API instance available'); return; @@ -15,12 +15,12 @@ const fetchLogEntries = async (api?: Api, options?: AxiosRequestConfig) => { return response.data; }; -export const useLogEntries = () => { +export const useServerLogs = () => { const { api } = useApi(); return useQuery({ queryKey: [ 'LogEntries' ], - queryFn: ({ signal }) => fetchLogEntries(api, { signal }), + queryFn: ({ signal }) => fetchServerLogs(api, { signal }), enabled: !!api }); }; diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index a348bcae62..708512eb89 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -7,7 +7,7 @@ import globalize from 'lib/globalize'; import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typography } from '@mui/material'; import { type ActionFunctionArgs, Form, useActionData } from 'react-router-dom'; import ServerConnections from 'components/ServerConnections'; -import { useLogEntries } from 'apps/dashboard/features/logs/api/useLogEntries'; +import { useServerLogs } from 'apps/dashboard/features/logs/api/useServerLogs'; import { useLogOptions } from 'apps/dashboard/features/logs/api/useLogOptions'; import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; import { ActionData } from 'types/actionData'; @@ -41,7 +41,7 @@ const Logs = () => { const actionData = useActionData() as ActionData | undefined; const [ isSubmitting, setIsSubmitting ] = useState(false); - const { isPending: isLogEntriesPending, data: logs } = useLogEntries(); + const { isPending: isLogEntriesPending, data: logs } = useServerLogs(); const { isPending: isLogOptionsPending, data: defaultLogOptions } = useLogOptions(); const [ loading, setLoading ] = useState(true); const [ logOptions, setLogOptions ] = useState( {} ); From ec6e0d368b181965421e112c893300bc9668f21e Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:10:06 +0300 Subject: [PATCH 13/18] Rename to useConfiguration --- src/apps/dashboard/routes/logs/index.tsx | 12 ++++++------ .../useLogOptions.ts => hooks/useConfiguration.ts} | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) rename src/{apps/dashboard/features/logs/api/useLogOptions.ts => hooks/useConfiguration.ts} (70%) diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 708512eb89..e1ff781cd7 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -8,7 +8,7 @@ import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typogra import { type ActionFunctionArgs, Form, useActionData } from 'react-router-dom'; import ServerConnections from 'components/ServerConnections'; import { useServerLogs } from 'apps/dashboard/features/logs/api/useServerLogs'; -import { useLogOptions } from 'apps/dashboard/features/logs/api/useLogOptions'; +import { useConfiguration } from 'hooks/useConfiguration'; import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; import { ActionData } from 'types/actionData'; @@ -42,16 +42,16 @@ const Logs = () => { const [ isSubmitting, setIsSubmitting ] = useState(false); const { isPending: isLogEntriesPending, data: logs } = useServerLogs(); - const { isPending: isLogOptionsPending, data: defaultLogOptions } = useLogOptions(); + const { isPending: isConfigurationPending, data: defaultConfiguration } = useConfiguration(); const [ loading, setLoading ] = useState(true); const [ logOptions, setLogOptions ] = useState( {} ); useEffect(() => { - if (!isLogOptionsPending && defaultLogOptions) { - setLogOptions(defaultLogOptions); + if (!isConfigurationPending && defaultConfiguration) { + setLogOptions(defaultConfiguration); setLoading(false); } - }, [isLogOptionsPending, defaultLogOptions]); + }, [isConfigurationPending, defaultConfiguration]); const setLogWarningMessage = useCallback((_: ChangeEvent, checked: boolean) => { setLogOptions({ @@ -71,7 +71,7 @@ const Logs = () => { setIsSubmitting(true); }, []); - if (isLogEntriesPending || isLogOptionsPending || loading) { + if (isLogEntriesPending || isConfigurationPending || loading) { return ; } diff --git a/src/apps/dashboard/features/logs/api/useLogOptions.ts b/src/hooks/useConfiguration.ts similarity index 70% rename from src/apps/dashboard/features/logs/api/useLogOptions.ts rename to src/hooks/useConfiguration.ts index a8c4bf07e5..2d5dbd5a2a 100644 --- a/src/apps/dashboard/features/logs/api/useLogOptions.ts +++ b/src/hooks/useConfiguration.ts @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import type { AxiosRequestConfig } from 'axios'; -export const fetchLogOptions = async (api?: Api, options?: AxiosRequestConfig) => { +export const fetchConfiguration = async (api?: Api, options?: AxiosRequestConfig) => { if (!api) { console.error('[useLogOptions] No API instance available'); return; @@ -15,12 +15,12 @@ export const fetchLogOptions = async (api?: Api, options?: AxiosRequestConfig) = return response.data; }; -export const useLogOptions = () => { +export const useConfiguration = () => { const { api } = useApi(); return useQuery({ - queryKey: ['LogOptions'], - queryFn: ({ signal }) => fetchLogOptions(api, { signal }), + queryKey: ['Configuration'], + queryFn: ({ signal }) => fetchConfiguration(api, { signal }), enabled: !!api }); }; From 30204a4db526c519feef196bc812154f6ed72395 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:17:56 +0300 Subject: [PATCH 14/18] Disable text input if option is disabled --- src/apps/dashboard/routes/logs/index.tsx | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index e1ff781cd7..8ed2633e12 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -20,9 +20,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { const { data: config } = await getConfigurationApi(api).getConfiguration(); const enableWarningMessage = formData.get('EnableWarningMessage'); - if (enableWarningMessage) { - config.EnableSlowResponseWarning = formData.get('EnableWarningMessage') === 'on'; - } + config.EnableSlowResponseWarning = enableWarningMessage === 'on'; const responseTime = formData.get('SlowResponseTime'); if (responseTime) { @@ -44,28 +42,28 @@ const Logs = () => { const { isPending: isLogEntriesPending, data: logs } = useServerLogs(); const { isPending: isConfigurationPending, data: defaultConfiguration } = useConfiguration(); const [ loading, setLoading ] = useState(true); - const [ logOptions, setLogOptions ] = useState( {} ); + const [ configuration, setConfiguration ] = useState( {} ); useEffect(() => { if (!isConfigurationPending && defaultConfiguration) { - setLogOptions(defaultConfiguration); + setConfiguration(defaultConfiguration); setLoading(false); } }, [isConfigurationPending, defaultConfiguration]); const setLogWarningMessage = useCallback((_: ChangeEvent, checked: boolean) => { - setLogOptions({ - ...logOptions, + setConfiguration({ + ...configuration, EnableSlowResponseWarning: checked }); - }, [logOptions]); + }, [configuration]); const onResponseTimeChange = useCallback((event: ChangeEvent) => { - setLogOptions({ - ...logOptions, + setConfiguration({ + ...configuration, SlowResponseThresholdMs: parseInt(event.target.value, 10) }); - }, [logOptions]); + }, [configuration]); const onSubmit = useCallback(() => { setIsSubmitting(true); @@ -97,7 +95,7 @@ const Logs = () => { @@ -110,7 +108,8 @@ const Logs = () => { type='number' name={'SlowResponseTime'} label={globalize.translate('LabelSlowResponseTime')} - value={logOptions?.SlowResponseThresholdMs} + value={configuration?.SlowResponseThresholdMs} + disabled={!configuration?.EnableSlowResponseWarning} onChange={onResponseTimeChange} /> From 941da45faa00219ccbd7185c5d3b46beb1a39bce Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:57:43 +0300 Subject: [PATCH 15/18] Move server logs to mui --- .../features/logs/api/useServerLogs.ts | 4 +-- .../features/logs/components/LogItem.tsx | 26 ++++++++++++------- src/apps/dashboard/routes/logs/index.tsx | 18 ++++++------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/apps/dashboard/features/logs/api/useServerLogs.ts b/src/apps/dashboard/features/logs/api/useServerLogs.ts index b0e2001523..fa00dc9afa 100644 --- a/src/apps/dashboard/features/logs/api/useServerLogs.ts +++ b/src/apps/dashboard/features/logs/api/useServerLogs.ts @@ -6,7 +6,7 @@ import type { AxiosRequestConfig } from 'axios'; const fetchServerLogs = async (api?: Api, options?: AxiosRequestConfig) => { if (!api) { - console.error('[useLogEntries] No API instance available'); + console.error('[useServerLogs] No API instance available'); return; } @@ -19,7 +19,7 @@ export const useServerLogs = () => { const { api } = useApi(); return useQuery({ - queryKey: [ 'LogEntries' ], + queryKey: [ 'ServerLogs' ], queryFn: ({ signal }) => fetchServerLogs(api, { signal }), enabled: !!api }); diff --git a/src/apps/dashboard/features/logs/components/LogItem.tsx b/src/apps/dashboard/features/logs/components/LogItem.tsx index be4c0193cc..4bf871c0c9 100644 --- a/src/apps/dashboard/features/logs/components/LogItem.tsx +++ b/src/apps/dashboard/features/logs/components/LogItem.tsx @@ -1,7 +1,7 @@ -import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; -import LinkButton from 'elements/emby-button/LinkButton'; -import { useApi } from 'hooks/useApi'; import React, { FunctionComponent } from 'react'; +import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; +import { Card, CardActionArea, CardContent, ListItemText } from '@mui/material'; +import { useApi } from 'hooks/useApi'; import datetime from 'scripts/datetime'; type LogItemProps = { @@ -12,7 +12,7 @@ const LogItem: FunctionComponent = ({ logFile }: LogItemProps) => const { api } = useApi(); const getLogFileUrl = () => { - if (!api) return; + if (!api) return ''; let url = api.basePath + '/System/Logs/Log'; @@ -28,12 +28,18 @@ const LogItem: FunctionComponent = ({ logFile }: LogItemProps) => }; return ( - -
-

{logFile.Name}

-
{getDate()}
-
-
+ + + + + + + ); }; diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 8ed2633e12..1e509f2314 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -121,16 +121,14 @@ const Logs = () => { -
-
- {logs?.map(log => { - return ; - })} -
-
+ + {logs?.map(log => { + return ; + })} + ); From 23c9e75dd200626d90a090783cad07cc91700bb6 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:42:26 +0300 Subject: [PATCH 16/18] Update to use list --- .../features/logs/components/LogItem.tsx | 46 ---------------- .../features/logs/components/LogItemList.tsx | 53 +++++++++++++++++++ src/apps/dashboard/routes/logs/index.tsx | 11 ++-- 3 files changed, 56 insertions(+), 54 deletions(-) delete mode 100644 src/apps/dashboard/features/logs/components/LogItem.tsx create mode 100644 src/apps/dashboard/features/logs/components/LogItemList.tsx diff --git a/src/apps/dashboard/features/logs/components/LogItem.tsx b/src/apps/dashboard/features/logs/components/LogItem.tsx deleted file mode 100644 index 4bf871c0c9..0000000000 --- a/src/apps/dashboard/features/logs/components/LogItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; -import { Card, CardActionArea, CardContent, ListItemText } from '@mui/material'; -import { useApi } from 'hooks/useApi'; -import datetime from 'scripts/datetime'; - -type LogItemProps = { - logFile: LogFile; -}; - -const LogItem: FunctionComponent = ({ logFile }: LogItemProps) => { - const { api } = useApi(); - - const getLogFileUrl = () => { - if (!api) return ''; - - let url = api.basePath + '/System/Logs/Log'; - - url += '?name=' + encodeURIComponent(String(logFile.Name)); - url += '&api_key=' + encodeURIComponent(api.accessToken); - - return url; - }; - - const getDate = () => { - const date = datetime.parseISO8601Date(logFile.DateModified, true); - return datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); - }; - - return ( - - - - - - - - ); -}; - -export default LogItem; diff --git a/src/apps/dashboard/features/logs/components/LogItemList.tsx b/src/apps/dashboard/features/logs/components/LogItemList.tsx new file mode 100644 index 0000000000..702c4594b4 --- /dev/null +++ b/src/apps/dashboard/features/logs/components/LogItemList.tsx @@ -0,0 +1,53 @@ +import React, { FunctionComponent } from 'react'; +import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; +import { Box, List, ListItem, ListItemButton, ListItemText, useTheme } from '@mui/material'; +import { useApi } from 'hooks/useApi'; +import datetime from 'scripts/datetime'; + +type LogItemProps = { + logs: LogFile[]; +}; + +const LogItemList: FunctionComponent = ({ logs }: LogItemProps) => { + const { api } = useApi(); + const theme = useTheme(); + + const getLogFileUrl = (logFile: LogFile) => { + if (!api) return ''; + + let url = api.basePath + '/System/Logs/Log'; + + url += '?name=' + encodeURIComponent(String(logFile.Name)); + url += '&api_key=' + encodeURIComponent(api.accessToken); + + return url; + }; + + const getDate = (logFile: LogFile) => { + const date = datetime.parseISO8601Date(logFile.DateModified, true); + return datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); + }; + + return ( + + + {logs.map(log => { + return ( + + + + + + ); + })} + + + ); +}; + +export default LogItemList; diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 1e509f2314..6f9826c37e 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -1,6 +1,5 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; -import LogItem from 'apps/dashboard/features/logs/components/LogItem'; import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; import globalize from 'lib/globalize'; @@ -11,6 +10,7 @@ import { useServerLogs } from 'apps/dashboard/features/logs/api/useServerLogs'; import { useConfiguration } from 'hooks/useConfiguration'; import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; import { ActionData } from 'types/actionData'; +import LogItemList from 'apps/dashboard/features/logs/components/LogItemList'; export const action = async ({ request }: ActionFunctionArgs) => { const api = ServerConnections.getCurrentApi(); @@ -69,7 +69,7 @@ const Logs = () => { setIsSubmitting(true); }, []); - if (isLogEntriesPending || isConfigurationPending || loading) { + if (isLogEntriesPending || isConfigurationPending || loading || !logs) { return ; } @@ -122,12 +122,7 @@ const Logs = () => { - {logs?.map(log => { - return ; - })} + From 1dede0ce2b68dbb5aaabf3f7341a0ee01b085eca Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:11:12 +0300 Subject: [PATCH 17/18] Simplify code, use standard attributes --- .../features/logs/components/LogItemList.tsx | 37 +++++++++---------- src/apps/dashboard/routes/logs/index.tsx | 4 +- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/apps/dashboard/features/logs/components/LogItemList.tsx b/src/apps/dashboard/features/logs/components/LogItemList.tsx index 702c4594b4..ce68f8bdd7 100644 --- a/src/apps/dashboard/features/logs/components/LogItemList.tsx +++ b/src/apps/dashboard/features/logs/components/LogItemList.tsx @@ -1,6 +1,6 @@ import React, { FunctionComponent } from 'react'; import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; -import { Box, List, ListItem, ListItemButton, ListItemText, useTheme } from '@mui/material'; +import { List, ListItem, ListItemButton, ListItemText } from '@mui/material'; import { useApi } from 'hooks/useApi'; import datetime from 'scripts/datetime'; @@ -10,7 +10,6 @@ type LogItemProps = { const LogItemList: FunctionComponent = ({ logs }: LogItemProps) => { const { api } = useApi(); - const theme = useTheme(); const getLogFileUrl = (logFile: LogFile) => { if (!api) return ''; @@ -29,24 +28,22 @@ const LogItemList: FunctionComponent = ({ logs }: LogItemProps) => }; return ( - - - {logs.map(log => { - return ( - - - - - - ); - })} - - + + {logs.map(log => { + return ( + + + + + + ); + })} + ); }; diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 6f9826c37e..8ab9decee8 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -121,9 +121,9 @@ const Logs = () => { - + - + ); From 11da1312ce8ecfc3f160382eaf051e35e5fcaceb Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:29:05 +0300 Subject: [PATCH 18/18] Apply review feedback --- .../features/logs/components/LogItemList.tsx | 8 +++++++- src/apps/dashboard/routes/logs/index.tsx | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/apps/dashboard/features/logs/components/LogItemList.tsx b/src/apps/dashboard/features/logs/components/LogItemList.tsx index ce68f8bdd7..3b72120faa 100644 --- a/src/apps/dashboard/features/logs/components/LogItemList.tsx +++ b/src/apps/dashboard/features/logs/components/LogItemList.tsx @@ -1,6 +1,10 @@ import React, { FunctionComponent } from 'react'; import type { LogFile } from '@jellyfin/sdk/lib/generated-client/models/log-file'; -import { List, ListItem, ListItemButton, ListItemText } from '@mui/material'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { useApi } from 'hooks/useApi'; import datetime from 'scripts/datetime'; @@ -11,6 +15,7 @@ type LogItemProps = { const LogItemList: FunctionComponent = ({ logs }: LogItemProps) => { const { api } = useApi(); + // TODO: Use getUri from TS SDK once available. const getLogFileUrl = (logFile: LogFile) => { if (!api) return ''; @@ -39,6 +44,7 @@ const LogItemList: FunctionComponent = ({ logs }: LogItemProps) => secondary={getDate(log)} secondaryTypographyProps={{ variant: 'body1' }} /> + ); diff --git a/src/apps/dashboard/routes/logs/index.tsx b/src/apps/dashboard/routes/logs/index.tsx index 8ab9decee8..bafab8f763 100644 --- a/src/apps/dashboard/routes/logs/index.tsx +++ b/src/apps/dashboard/routes/logs/index.tsx @@ -3,7 +3,14 @@ import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-a import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; import globalize from 'lib/globalize'; -import { Alert, Box, Button, FormControlLabel, Stack, Switch, TextField, Typography } from '@mui/material'; +import Alert from '@mui/material/Alert'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Stack from '@mui/material/Stack'; +import Switch from '@mui/material/Switch'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; import { type ActionFunctionArgs, Form, useActionData } from 'react-router-dom'; import ServerConnections from 'components/ServerConnections'; import { useServerLogs } from 'apps/dashboard/features/logs/api/useServerLogs'; @@ -121,7 +128,7 @@ const Logs = () => { - +